There is a lot of talk on the Internet about new features of C ++ 11: auto, lambda, variadic templates. But somehow they avoided the new possibilities of working with the exceptions provided by the language and the standard library.
From the previous version of the standard, there was an exception generation mechanism (throw), a check that we are in the process of handling an exception (std :: uncaught_exception), a stop mechanism if the exception was not processed. There is also a hierarchy of standard exceptions based on the class std :: exception.
The new standard adds a few more entities to these things, which, in my opinion, can significantly simplify work with exceptions in C ++.
')
exception_ptr
So, the very first thing we can 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. This may be a typedef, it may be a class implementation. Its behavior is similar to that of std :: shared_ptr, that is, it can be copied, passed as a parameter, and 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 can 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(ev) { 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: " << ev << std::endl; } std::exception_ptr anotherExceptionPtr = ePtr; try { if (anotherExceptionPtr) { std::rethrow_exception(anotherExceptionPtr); } } catch (const some_exception & e) { std::cout << "catched int value: " << ev << 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 function
exceptionPtrSample for execution, we will see something like the following result:
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 a catch block, it returns a exception_ptr that contains the exception currently being processed by the current thread, if calling it outside the catch block, it will return an empty exception_ptr object
- rethrow_exception - this function throws an exception that 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 throwExceptionAndCaptureExceptionPtr function . In the implementation from gcc-4.7.2, make_exception_ptr makes two copies of the object some_exception.
Passing Exceptions Between Threads
It seems to me that the type of exception_ptr was created specifically to solve the problem of passing exceptions between threads, so let's see how we can pass an exception from one thread to another:
void worker(std::promise<void> & p) { try { throw std::runtime_error("exception from thread"); } catch (...) { p.set_exception(std::current_exception()); } } void checkThreadAndException() { std::promise<void> 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 separately. We will now consider this example just for the sake of passing an exception. We start the
worker function in a separate thread, and this function throws an exception. The object of the promise class allows you to organize the connection between different threads, and atomically transfer values ​​from one stream to another (or exception). In this example, we just use the
set_exception method, which accepts exception_ptr as a parameter. In order to get the value, we create an object of the
future class - this is our result and we call the
get method. It is also necessary for the thread to call the
detach or
join method, since when the object is destroyed,
t in the destructor is checked to joinable () == false, otherwise std :: terminate is called. Most likely, this is due to the fact that the programmer does not "release the threads at will", but always followed them (or explicitly released using the
detach method)
We should also mention the use of multithreading in gcc-4.7. Initially, this example did not work for me (I threw out an exception), while googling, I found out that in order 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 (there may be difficulty when using gcc on different platforms, for example, the -thread flag is used instead of -pthread on sparc solaris) - there is a special CMake module Threads, 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 (which could have been thrown earlier) to the thrown exception. 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 handling our exceptions, we can add extra. information that is “attached” to them (for example, when debugging, we can print information about third-party exceptions). A good example of using nested exception is provided at
cppreference.com , my example, overlaps with it in part:
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 from them makes "our" exception: the
third_party_worker and
ourWorker act as the "third-party" function and "our" function respectively. We catch all exceptions, and we throw further our (our_exception) exception, at the same time, some kind of “third-party” exception was attached to it (we can in principle not even know what). After that, we work with our exceptions. At the same time, if we need more detailed information about what was happening at the “lower” level, then we can always call the
rethrow_if_nested function. This function analyzes if there is a hooked (nested) exception, and if so, throws this nested exception. The function
exceptionHandler accepts "our" exception and an additional flag that allows or prohibits the display 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, depending on the build, Release, Debug).
To work with nested exception there is one class and two functions:
- nested_exception - this class is “mixed in” to the object being thrown when the std :: throw_with_nested function is called — this class also allows you to return a nested exception (exception_ptr) using the nested_ptr method. This class also has a rethrow_nested method that throws a nested exception.
- throw_with_nested - this template function accepts an object of some type (let's call it InputType ), and throws an object that is the heir 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 if an object has a nested exception, and if so, throws it. An implementation can 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 implementation of gcc, the
nested_exception class contains a single field of type
exception_ptr , which is initialized in the constructor using the
current_exception function, and the implementation of the
rethrow_nested method 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 warranty is violated, then std :: terminate is called).
This mechanism is used in two forms, similar to the old throw ()
void func() noexcept {
And in the new form:
void func() noexcept(boolean_expression_known_at_compile_time) {
In this case, 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 a corresponding noexcept (expression) operator, which is also executed in 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<int>(): " << noexcept(vector<int>()) << endl; }
This code for gcc-4.7.2 displays:
noexcept int (): 1
noexcept vector <int> (): 0
Here we see that the constructor of the built-in type int does not throw an exception, and the vector constructor 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 <typename InputTypeT> 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<int>(): " << noexcept(std::vector<int>()) << std::endl << std::endl; /// @note function is not actually called std::cout << "noexcept func<int>: " << noexcept(func<int>()) << std::endl; std::cout << "noexcept func<vector<int>>: " << noexcept(func<std::vector<int>>()) << std::endl; }
This example displays:
noexcept int (): 1
noexcept vector <int> (): 0
noexcept func <int>: 1
noexcept func <vector <int >>: 0
Summary
The C ++ 11 standard introduced a lot of new things in error handling, of course, the key feature here is exception_ptr and the ability to pass arbitrary exceptions as ordinary objects (in functions, to pass exceptions between threads). Earlier, in each thread, we had to write a spreading try ... catch for all exceptions, and this functionality minimizes the amount of try ... catch code.
It also became possible to create nested exceptions, in principle in the boost library there is a mechanism
boost :: exception , which allows attaching arbitrary data to the object (for example, error codes, its own messages, etc.), but it solves other tasks - the transfer of arbitrary data inside the exception.
And finally, for those who need guarantees that the code will not throw exceptions, there is
noexcept , in particular, many parts of the standard library use this mechanism.
As usual, all the examples are posted on
github.Update 1. Removed from examples using namespace std, now you can see which entities belong to the standard library, and which are not.