📜 ⬆️ ⬇️

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

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:


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:

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.

Source: https://habr.com/ru/post/160955/


All Articles