What's new in working with exceptions in C ++ 11

    On the Internet, they talk a lot about the new features of C ++ 11: auto, lambda, variadic templates. But somehow we bypassed the new opportunities for working with exceptions that the language and the standard library provide.

    From the previous version of the standard, there was left a mechanism for throwing exceptions (throw), checking that we are in the process of handling the exception (std :: uncaught_exception), a stopping mechanism if the exception was not handled. There is also a hierarchy of standard exceptions based on the std :: exception class.

    The new standard adds a few more entities to these things, which, in my opinion, can greatly simplify the work with exceptions in C ++.



    exception_ptr

    So, the very first thing we may encounter is std :: exception_ptr. This type allows you to store an exception of absolutely any type. The standard does not specify how this type is obtained. It can be a typedef, it can be an implementation of a class. Its behavior is similar to the behavior of std :: shared_ptr, that is, it can be copied, passed as a parameter, while the exception itself is not copied. The main purpose of exception_ptr is to pass exceptions as function parameters, it is possible to pass exceptions between threads. Thus, objects of this type make error handling more flexible:
    struct some_exception {
    	explicit some_exception(int x): v(x) {
    		std::cout << " int ctor" << std::endl;
    	}
    	some_exception(const some_exception & e): v(e.v) {
    		std::cout << " copy ctor" << std::endl;
    	}
    	int v;
    };
    std::exception_ptr throwExceptionAndCaptureExceptionPtr() {
    	std::exception_ptr currentException;
    	try {
    		const int throwValue = 10;
    		std::cout << "throwing           " << throwValue << "..." << std::endl;
    		throw some_exception(throwValue);
    	} catch (...) {
    		 currentException = std::current_exception();
    	}
    	return currentException;
    }
    void rethrowException(std::exception_ptr ePtr) {
    	try {
    		if (ePtr) {
    			std::rethrow_exception(ePtr);
    		}
    	} catch (const some_exception & e) {
    		std::cout << "catched int value: " << e.v << std::endl;
    	}
    	std::exception_ptr anotherExceptionPtr = ePtr;
    	try {
    		if (anotherExceptionPtr) {
    			std::rethrow_exception(anotherExceptionPtr);
    		}
    	} catch (const some_exception & e) {
    		std::cout << "catched int value: " << e.v << std::endl;
    	}
    }
    void checkMakeExceptionPtr() {
    	std::exception_ptr currentException = std::make_exception_ptr(some_exception(20));
    	std::cout << "exception_ptr constructed" << std::endl;
    	rethrowException(currentException);
    }
    void exceptionPtrSample() {
    	rethrowException(throwExceptionAndCaptureExceptionPtr());
    	checkMakeExceptionPtr();
    }

    If we run the exceptionPtrSample function for execution, we will see something like the following:
    throwing 10 ...
    int ctor
    catched int value: 10
    catched int value: 10
    int ctor
    copy ctor
    copy ctor
    exception_ptr constructed
    catched int value: 20
    catched int value: 20

    In order to be able to conveniently work with exception_ptr, there are several auxiliary functions:
    • current_exception - this function returns exception_ptr. If we are inside the catch block, then it returns exception_ptr, which contains the exception currently being processed by the current thread; if you call it outside the catch block, it will return an empty exception_ptr object
    • rethrow_exception - this function throws an exception, which is contained in exception_ptr. If the input parameter does not contain an exception (empty object), then the result is undefined. In this case, msvc throws std :: bad_exception , and the program compiled with gcc-4.7.2 terminates unexpectedly .
    • make_exception_ptr - this function can construct exception_ptr without throwing an exception. Its purpose is similar to the function std :: make_shared - constructing an object. Its execution is similar to the function throwExceptionAndCaptureExceptionPtr. In the implementation of gcc-4.7.2, make_exception_ptr makes two copies of the some_exception object.


    Passing exceptions between threads

    It seems to me that the type exception_ptr was created specifically to solve the problem of passing exceptions between threads, so let's figure out how we can pass an exception from one thread to another:
    void worker(std::promise & p) {
    	try {
    		throw std::runtime_error("exception from thread");
    	} catch (...) {
    		p.set_exception(std::current_exception());
    	}
    }
    void checkThreadAndException() {
    	std::promise p;
    	auto result = p.get_future();
    	std::thread t(worker, ref(p));
    	t.detach();
    	try {
    		result.get();
    	} catch (const std::runtime_error & e) {
    		std::cout << "runtime error catched from async worker" << std::endl;
    	}
    }

    In general, multithreading in C ++ 11 is an extensive topic, it has its own subtleties, nuances and should be written about them separately. Now we will consider this example only for the sake of passing an exception. We run the worker function in a separate thread, and this function throws an exception. An object of the promise class allows you to organize communication between different threads, and atomically transfer values ​​from one thread to another (or an exception). In this example, we are just using the set_exception method , which takes exception_ptr as a parameter. In order to get the value, we create an object of the future class - this is our result and call the get method . You must also call the detach or join method on the stream., since when the object t is destroyed in the destructor, it is checked that joinable () == false, otherwise std :: terminate is called. Most likely, this is due to the fact that the programmer does not "let the threads go free", but always follows them (or releases them explicitly using the detach method ).

    It is worth mentioning the use of multithreading in gcc-4.7. Initially, this example did not work for me (throwing an exception), google, I found out that to use std :: thread it is necessary to pass the -pthread flag to the linker. Since I use CMake as a build system, this task is simplified (here complexity can arise when using gcc on different platforms, for example, the -thread flag is used instead of -pthread on sparc solaris) - there is a special Threads CMake module in which this problem is solved :
    find_package (Threads REQUIRED)
    # ...
    target_link_libraries (cxx_exceptions $ {CMAKE_THREAD_LIBS_INIT})


    Nested exceptions

    As the name implies, this mechanism allows you to “attach” other exceptions to the thrown exception (which could have been thrown earlier). For example, if we have our own hierarchy of exceptions, then we can catch all the “third-party” exceptions, attach them to our exceptions, and when processing our exceptions, we can derive extra. information that is “attached” to them (for example, during debugging, we can print information about third-party exceptions). A good example of using nested exception is given at cppreference.com , my example partially overlaps with it:
    struct third_party_exception {
    	explicit third_party_exception(int e) : code(e) {
    	}
    	int code;
    };
    void third_party_worker() {
    	throw third_party_exception(100);
    }
    class our_exception : public std::runtime_error {
    public:
    	our_exception(const std::string & e) : std::runtime_error("our error: " + e) {
    	}
    };
    void ourWorker() {
    	try {
    		third_party_worker();
    	} catch (...) {
    		throw_with_nested(our_exception("worker failed"));
    	}
    }
    void exceptionHandler(const our_exception & e, bool catchNested) {
    	std::cerr << "our exception catched: " << e.what();
    	if (catchNested) {
    		try {
    			rethrow_if_nested(e);
    		} catch (const third_party_exception & e) {
    			std::cerr << ", low level reason is: " << e.code;
    		}
    	}
    	std::cerr << std::endl;
    }
    void exceptionProcessing() {
    	try {
    		ourWorker();
    	} catch (const our_exception & e) {
    		exceptionHandler(e, false);
    	}
    	try {
    		ourWorker();
    	} catch (const our_exception & e) {
    		exceptionHandler(e, true);
    	}
    }


    So, we have a third-party function that throws an exception, we can write an adapter that catches "third-party" exceptions, and one of them makes our exception: the third_party_worker and ourWorker respectively act as the "third-party" function and "our" function . We catch all exceptions, and then throw our (our_exception) exception, and at the same time, some (we, in principle, may not know which) “third-party” exception was attached to it. After that, we are already working with our exceptions. Moreover, if we need more detailed information about what happened at the "lower" level, then we can always call the rethrow_if_nested function. This function analyzes if a hooked (nested) exception is thrown, and if so, throws this nested exception. The exceptionHandler function accepts “our” exception and an additional flag that allows or prohibits the output of information about a third-party exception. We can control the output of third-party exceptions by controlling the catchNested parameter, for example, from the configuration file (or Release, Debug, depending on the assembly).

    To work with nested exception, there is one class and two functions:
    • nested_exception - this class "mixes" with the throwing object when calling the std :: throw_with_nested function - this class also allows you to return a nested exception (exception_ptr) using the nested_ptr method . Also this class has a rethrow_nested method that throws a nested exception
    • throw_with_nested - this template function takes an object of some type (let's call it InputType ), and throws an object that is a descendant of std :: nested_exception and our InputType (in the implementation from gcc, it is a template type that inherits from nested_exception and InputType). Thus, we can catch both an object of our type and an object of type nested_exception and only then get our type through the nested_ptr method
    • rethrow_if_nested - this function determines whether the object has a nested exception, and if so, throws it. An implementation may use dynamic_cast to determine inheritance

    In principle, the nested exception mechanism is quite interesting, although the implementation can be quite trivial, you can do it yourself using the previously described functions current_exception and rethrow_exception . In the same gcc implementation, the nested_exception class contains one field of type exception_ptr , which is initialized in the constructor using the current_exception function , and the rethrow_nested method implementation simply calls the rethrow_exception function .

    Noexcept specification

    This mechanism can be considered as an extended (and now obsolete) throw () mechanism. Its main purpose, as before, is to ensure that the function does not throw an exception (if the guarantee is violated, std :: terminate is called).

    This mechanism is used in two ways, similar to the old throw ()
    void func() noexcept {
    //...
    }


    And in a new form:
    void func() noexcept(boolean_expression_known_at_compile_time) {
    //...
    }

    Moreover, if the value of the expression is calculated as true, then the function is marked as noexcept, otherwise, there is no such guarantee.
    There is also the corresponding noexcept (expression) operator, which also runs at compile time, this operator returns true if the expression does not throw an exception:
    void noexceptSample() {
    	cout << "noexcept int():         " << noexcept(int()) << endl;
    	cout << "noexcept vector(): " << noexcept(vector()) << endl;
    }
    

    This code for gcc-4.7.2 displays:
    noexcept int (): 1
    noexcept vector(): 0


    Here we see that the constructor of the int int does not throw an exception, and the constructor of the vector can throw (not marked as noexcept).
    This is convenient to use in template metaprogramming, using this operator we can write a template function, which, depending on the template parameter, can be marked as noexcept or not:
    template 
    void func() noexcept(noexcept(InputTypeT())) {
    	InputTypeT var;
    	/// do smth with var
    	std::cout << "func called, object size: " << sizeof(var) << std::endl;
    }
    void noexceptSample() {
    	std::cout << "noexcept int():         " << noexcept(int()) << std::endl;
    	std::cout << "noexcept vector(): " << noexcept(std::vector()) << std::endl << std::endl;
    	/// @note function is not actually called
    	std::cout << "noexcept func:         " << noexcept(func()) << std::endl;
    	std::cout << "noexcept func>: " << noexcept(func>()) << std::endl;
    }

    This example outputs:
    noexcept int (): 1
    noexcept vector(): 0

    noexcept func: 1
    noexcept func>: 0


    Summary

    The C ++ 11 standard has brought a lot of new things to error handling, of course, the key feature here is exception_ptr and the ability to pass arbitrary exceptions like regular objects (in functions, pass exceptions between threads). Previously, in every thread I had to write a forked try ... catch for all exceptions, and this functionality significantly minimizes the amount of try ... catch code.

    It was also possible to create nested exceptions, in principle, the boost library has a boost :: exception mechanism that allows you to attach arbitrary data to the object (for example, error codes, your messages, etc.), but it solves other problems - passing arbitrary data inside the exception.

    And finally, for those who need guarantees that the code will not throw an exception, there isnoexcept , in particular, many parts of the standard library use this mechanism.

    As usual, all examples are uploaded to github

    Update 1. Removed from examples using namespace std, now you can see which entities belong to the standard library and which do not.

    Also popular now: