go to start Ex W9
|home |print view |recent changes |changed April 25, 2018 |
exact
|You are 54.92.160.119 <- set your identity!

Sections: Exception-Safe Pair of Pointers | Safer Pointers with unique_ptr ? | Exception Safety of Bounded Buffer | SFINAE Applied | And Exception Safety... | SFINAE for Feature Detection (Tutorial) | Some Infrastructure | Resolution Step by Step | =[has_ostream_shift_t]= | =[is_outputtable]= | =[printer]= |

Exception-Safe Pair of Pointers ^

Take the example code from the lecture slides and implement a safer pointer class SafePtrPair using try-catch function bodies for constructor and destructor with plain pointers to a generic type.

void testSafePtrPair() {
	ctor_2nd_throws::reset();
	ASSERT_THROWS(SafePtrPair<ctor_2nd_throws>{},nasty);
	ASSERT_EQUAL(1,ctor_2nd_throws::deleted);
	ASSERT_EQUAL(1,ctor_2nd_throws::created);
}
void testSafePtrPairThrowingDestructor(){
	dtor_2nd_throws::reset();
	try {{
		SafePtrPair<dtor_2nd_throws> p{};
	}
	FAILM("expected throwing");
	} catch(nasty const &){
	} catch(cute::test_failure const &){
		throw;
	}
	catch(...){
		FAILM("something bad happened");
	}
	ASSERT_EQUAL(2,dtor_2nd_throws::created);
	ASSERT_EQUAL(1,dtor_2nd_throws::deleted);
}

Safer Pointers with unique_ptr ? ^

Do the same exercise with a class SaferPtrPair that implements its members using std::unique_ptr<T>. Is it really safer?

void testSaferPtrPair() {
	ctor_2nd_throws::reset();
	ASSERT_THROWS(SaferPtrPair<ctor_2nd_throws>{},nasty);
	ASSERT_EQUAL(1,ctor_2nd_throws::deleted);
	ASSERT_EQUAL(1,ctor_2nd_throws::created);
}
void testSaferPtrPairThrowingDestructor(){
	dtor_2nd_throws::reset();
	try {{
		SaferPtrPair<dtor_2nd_throws> p{};
	}
	FAILM("expected throwing");
	} catch(nasty const &){

	} catch(...){
		FAILM("something bad happened");
	}
}
Can you make the second test case above succeed? What happens when it runs?

Exception Safety of Bounded Buffer ^

You might have heard something about exception safety guarantees - if not read about it yourself. Here is an article by Bjarne Stroustrup about that topic: http://www.stroustrup.com/except.pdf There are four categories. Can you say which member functions of your BoundedBuffer versions guarantees what level of exception safety?

SFINAE Applied ^

Take your BoundedBuffer solution to your first Testat exercise. This solution relies on the ability to default-construct the element type, because a fixed sized array is used. That is actually the best way to solve it, unless you want to support non-default constructible types.

So a second implementation using a std::array<std::byte,N*sizeof(T)> is required.

To automatically switch between the two implementations a third bool template argument is needed.

template<typename T, size_t N, bool= std::is_default_constructible_v<T> >
struct BoundedBuffer;
resulting in two specializations (true with your existing implementation, false for the new one using the tricks you learned for the 2nd testat, but still being fixed size and fixed allocated space).

For convenience, here is a set with additional tests that test the compilability and behavior with classes that are not default constructible.

https:files/VorlageFixedBoundedBufferEx09.zip

And Exception Safety... ^

Can you make your all BoundedBuffer variations providing the strong exception guarantee?

Can you write Tests using the following nasty element types:

struct nasty{};

struct ThrowsOnCopy {
	ThrowsOnCopy()=default;
	ThrowsOnCopy(ThrowsOnCopy const &){
		throw nasty{};
	}
	ThrowsOnCopy(ThrowsOnCopy &&other) noexcept =default;
	ThrowsOnCopy& operator=(ThrowsOnCopy const&){
		throw nasty{};
	}
	ThrowsOnCopy& operator=(ThrowsOnCopy&& other)noexcept=default;
};

void BufferWithThrowsOnCopy(){
	BoundedBuffer<ThrowsOnCopy,2> buf{};
	buf.push(ThrowsOnCopy{});
	ASSERT_THROWS(buf.push(buf.front()),nasty);
	ASSERT_EQUAL(1,buf.size());
	ASSERT_THROWS((BoundedBuffer<ThrowsOnCopy,2>{buf}),nasty);
}

struct ThrowsOnCopyNonDefault {
	ThrowsOnCopyNonDefault(int){}
	ThrowsOnCopyNonDefault(ThrowsOnCopyNonDefault const &){
		throw nasty{};
	}
	ThrowsOnCopyNonDefault(ThrowsOnCopyNonDefault &&other) noexcept =default;
	ThrowsOnCopyNonDefault& operator=(ThrowsOnCopyNonDefault const&){
		throw nasty{};
	}
	ThrowsOnCopyNonDefault& operator=(ThrowsOnCopyNonDefault&& other)noexcept=default;
};

void BufferWithThrowsOnCopyNonDefault(){
	BoundedBuffer<ThrowsOnCopyNonDefault,2> buf{};
	ThrowsOnCopyNonDefault t{1};
	buf.push(ThrowsOnCopyNonDefault{1}); // move OK
	ASSERT_EQUAL(1,buf.size());
	ASSERT_THROWS(buf.push(t),nasty);
	ASSERT_EQUAL(1,buf.size());
}


SFINAE for Feature Detection (Tutorial) ^

Your goal is to provide a versatile print function that either uses the << operator of the type to print the value or to report a meaningful message. But in both cases the code shall compile.

Example:

struct X{};

int main(){
  printer(std::cout, 42); //prints: 42
  printer(std::cout, X{}); //prints: cannot print X
  printer(std::cout, std::cout); //prints: cannot print std::ostream
}

Some Infrastructure ^

Detecting a specific feature like the existence of the output operator (<<) requires some infrastructure. A proposal for the C++ standard (N4502 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf, http://en.cppreference.com/w/cpp/experimental/is_detected https://blog.tartanllama.xyz/detection-idiom/ ) suggests the implementation of the detect template. This template looks similar to the following:

template<typename, template<typename > class, typename = void_t<>>
struct detect : std::false_type {
};

template<typename T, template<typename > class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

The template detect is derived from std::false_type and empty otherwise. It takes three template paramters:

The default type, the template void_t is a C++17 meta-programming feature for consuming types. It acts as a sink for its arguments not further processing them. However, the parameters are still substituted with the arguments and can therefore provoke substitution failures. In this way specializations can be eliminated using SFINAE.

The specialized detect template is derived from std::true_type. It tries to instanciate the given template (second template parameter) with the given type (first parameter). Since the result of this application is not important that application happens inside the third template argument, which is void_t. This if T can be successfully used as template argument for the template template parameter (Op) this specialization is selected. This results in the specific detect instance being derived from std::true_type. Otherwise, if a substition failure happens, the base template is detected, resulting in detect being derived from std::false_type.

The std::void_t template could be implemented as follows. Use this definition if the standard library of your compiler does not provide it:

// used to swallow up superflous template arguments but check their validity
template<typename ...>
using void_t = void; // in std:: in C++17

With the infrastructure above you are able to specify predicates and apply them to types.

Resolution Step by Step ^

has_ostream_shift_t ^

First, create predicate to check whether your type can be "outputted" to std::ostream. Implement a template alias has_ostream_shift_t. This alias template will be the core for triggering SFINAE. The aliased type can be determined by decltype(). The effective type is not important, the primary goal is to determine whether decltype can determine the type of an expression using the output operator (<<) on the corresponding arguments. Let's examine a hypothetical expression:

out << v

out is of type std::ostream & and v is of an arbitrary type.

There are two possible outcomes:

The problem here is getting such hypothetical variables. You might just create an instance using T{}. But this does neithr work for types that don't have a default constructor nor can you create a reference (as you would need to std::ostream &). There exists a library feature to overcome this issue. The std::declval() template function materializes a value of the given type from nothing. As this feature does not sound like something possibly done in reality, it can only be used in unevaluated contexts. However, this is enough for our application in decltype, as the given expression is not evaluated.

With this information try to specify the has_ostream_shift_t alias template. If you are stuck on this task, see SolW9.

is_outputtable ^

Second, create an alias template for is_outputtable. The alias should be either for std::true_type or std::false_type depending on whether the given type template parameter is outputtable or not.

Use the detect and has_ostream_shift_t templates to specify it.

printer ^

Putting it all together. Specify the function template printer with two overloads:


|home |print view |recent changes |changed April 25, 2018 |
exact
|You are 54.92.160.119 <- set your identity!

Ex W9
go to start