Destructive exceptions

 3r3615. 3r3-31.
Once again about why it is bad to throw exceptions in destructors 3r33597. 3r3-3598.  3r3615. 3r3600. Many C ++ experts (for example, 3r337. Sutter's coat of arms
) Teach us that throwing exceptions in destructors is bad, because you can get into the destructor during stack promotion with an exception already thrown, and if at that moment another exception is thrown, the result will be called r3r3591. std :: terminate ()
. The standard of the C ++ 17 language (hereinafter I refer to the freely accessible version of the draft N4713 ) Tells us the following about this topic:
3r314. 3r3602. 3r3-3598.  3r3615.
??? The std :: terminate () function[except.terminate]
3r3-3598.  3r3615. 3r3-3598.  3r3615. 1 substitution error handling techniques.[Note:
These situations are:

(1.4) when the destruction of an object during stack unwinding (18.2) terminates by throwing an exception, or

— end note ]
3r3600. Check with a simple example:
3r3-3598.  3r3615. 3r? 3572. 3r33573. #include
3r3615. 3r3615. class PrintInDestructor {
3r3615. public: 3r315615. ~ PrintInDestructor () noexcept {
std :: cerr "~ PrintInDestructor () invokedn"; 3r3615.} 3r33615. 3r3615.}; 3r3615. 3r3615. void throw_int_func () {3r33615. std :: cerr "throw_int_func () invokedn"; 3r3615. throw 1; 3r3615.} 3r33615. 3r3615. class ThrowInDestructor {
3r3615. public: 3r315615. ~ ThrowInDestructor () noexcept (false) {
std :: cerr "~ ThrowInDestructor () invokedn"; 3r3615. throw_int_func (); 3r3615.} 3r33615. 3r3615. private: 3r31536. PrintInDestructor member_; 3r3615. 3r3615.}; 3r3615. 3r3615. int main (int, char **) {
3r3615. try {3r31515. ThrowInDestructor bad; 3r3615. throw "BANG!"; 3r3615.} catch (int i) {
std :: cerr "Catched int exception:" i "n"; 3r3615.} catch (const char * c) {3r33615. std :: cerr "Catched const char * exception:" c "n"; 3r3615.} catch () {
std :: cerr "Catched unknown exceptionn"; 3r3615.} 3r33615. 3r3615. return 0; 3r3615. 3r3615.} 3r3-3579. 3r3-3598.  3r3615. 3r3600. Result: 3r3603. 3r3-3598.  3r3615. 3r? 3572. 3r33573. ~ ThrowInDestructor () invoked
throw_int_func () invoked
~ PrintInDestructor () invoked
terminate called after throwing an instance of 'int'
Aborted 3r3-3579. 3r3-3598.  3r3615. 3r3600. Please note that destructor
PrintInDestructor
still being called, i.e. after throwing the second exception, the stack spinup is not interrupted. In the Standard (the same paragraph ???) on this subject, the following is said: 3r3-3603. 3r3-3598.  3r3615.
2 In the situation where there is no matching handler is found,
 3r3615. it is not necessary to determine whether it’s std :: terminate () is called. In
 3r3615. The situation for the handler (18.3) encounters a function with a
 3r3615. Non-throwing exception specification (18.4), whether it is unwound,
 3r3615. unwound partially or unwound at all before std :: terminate () is called
3r3600. I tested this example on several versions of
GCC 3r3207. (8.? 7.3) and 3r3206. Clang
(6.? 5.0), everywhere the stack promotion continues. If you find a compiler where implementation-defined is different, please write about it in the comments.
3r3-3598.  3r3615. 3r3600. It should be noted also that r3r3591. std :: terminate ()
during stack promotion, it is called only when an exception is thrown out of the destructor. If there is a try /catch block inside the destructor that catches an exception and does not forward further, this does not interrupt the unwinding of the outer exception stack.
3r3-3598.  3r3615. 3r? 3572. 3r33573. class ThrowCatchInDestructor {
3r3615. public: 3r315615. ~ ThrowCatchInDestructor () noexcept (false) {
try {3r31515. throw_int_func (); 3r3615.} catch (int i) {
std :: cerr "Catched int in ~ ThrowCatchInDestructor ():" i "n"; 3r3615.} 3r33615.} 3r33615. 3r3615. private: 3r31536. PrintInDestructor member_; 3r3615. 3r3615.}; 3r3615. 3r3615. int main (int, char **) {
3r3615. try {3r31515. ThrowCatchInDestructor good; 3r3615. std :: cerr "ThrowCatchInDestructor instance createdn"; 3r3615. throw "BANG!"; 3r3615.} catch (int i) {
std :: cerr "Catched int exception:" i "n"; 3r3615.} catch (const char * s) {3r336155. std :: cerr "Catched const char * exception:" s "n"; 3r3615.} catch () {
std :: cerr "Catched unknown exceptionn"; 3r3615.} 3r33615. 3r3615. return 0; 3r3615. 3r3615.} 3r3-3579. 3r3-3598.  3r3615. 3r3600. displays
3r3-3598.  3r3615. 3r? 3572. 3r33573. ThrowCatchInDestructor instance created
throw_int_func () invoked
Catched int in ~ ThrowCatchInDestructor (): 1
~ PrintInDestructor () invoked
Catched const char * exception: BANG! 3r33578. 3r3-3579. 3r3-3598.  3r3615. 3r3600. How to avoid unpleasant situations? In theory, everything is simple: never throw exceptions to the destructor. However, in practice it is not so easy to beautifully and elegantly implement this simple requirement.
3r3-3598.  3r3615.
If you can not, but really want 3r3r97. 3r3-3598.  3r3615.
Immediately, I note that I am not trying to justify throwing exceptions from the destructor, and following Satter, Meyers and other C ++ gurus, I urge you 3r3206. try
never do this (at least in the new code). However, a programmer in actual practice may well encounter a legacy code that does not easily lead to high standards. In addition, the techniques described below can often come in handy during the debugging process.
3r3600. For example, we are developing a library with a wrapper class that encapsulates working with a certain resource. In accordance with the principles of RAII, we capture the resource in the constructor and must free it in the destructor. But what if the attempt to free a resource ends in error? Options for solving this problem:
3r3-3598.  3r3615.
 3r3615.
Ignore the error. Bad, because we hide a problem that may affect other parts of the system.
 3r3615.
Write to the log. Better than just ignoring, but still bad, because our library does not know anything about the logging policies adopted in the system that uses it. The standard log can be redirected to /dev /null, with the result that, again, we will not see the error.
 3r3615.
To make the release of a resource into a separate function that returns a value or throws an exception, and force the class user to call it independently. Bad, because the user can forget to do it at all, and we get a resource leak.
 3r3615.
Throw an exception. Good in ordinary cases, because a class user can catch an exception and in a standard way obtain information about the error that occurred. Bad during stack promotion, because leads to r3r3591. std :: terminate ()
.
 3r3615.
3r3-3598.  3r3615. 3r3600. How to understand whether we are currently in the process of promotion of the stack on the exception or not? In C ++, there is a special function for this, 3r???. std :: uncaught_exception ()
. With its help, we can safely throw an exception in a normal situation, or do something less correct, but not leading to an exception being thrown during the stack promotion.
3r3-3598.  3r3615. 3r? 3572. 3r33573. class ThrowInDestructor {
3r3615. public: 3r315615. ~ ThrowInDestructor () noexcept (false) {
if (std :: uncaught_exception ()) {
std :: cerr "~ ThrowInDestructor () stack unwinding, not throwingn"; 3r3615.} else {
std :: cerr "~ ThrowInDestructor () normal case, throwingn"; 3r3615. throw_int_func (); 3r3615.} 3r33615.} 3r33615. 3r3615. private: 3r31536. PrintInDestructor member_; 3r3615. 3r3615.}; 3r3615. 3r3615. int main (int, char **) {
3r3615. try {3r31515. ThrowInDestructor normal; 3r3615. std :: cerr "ThrowInDestructor normal destructionn"; 3r3615.} catch (int i) {
std :: cerr "Catched int exception:" i "n"; 3r3615.} 3r33615. 3r3615. try {3r31515. ThrowInDestructor stack_unwind; 3r3615. std :: cerr "ThrowInDestructor stack unwindingn"; 3r3615. throw "BANG!"; 3r3615.} catch (int i) {
std :: cerr "Catched int exception:" i "n"; 3r3615.} catch (const char * s) {3r336155. std :: cerr "Catched const char * exception:" s "n"; 3r3615.} catch () {
std :: cerr "Catched unknown exceptionn"; 3r3615.} 3r33615. 3r3615. return 0; 3r3615. 3r3615.} 3r3-3579. 3r3-3598.  3r3615. 3r3600. Result: 3r3603. 3r3-3598.  3r3615. 3r? 3572. 3r33573. ThrowInDestructor normal destruction
~ ThrowInDestructor () normal case, throwing
throw_int_func () invoked
~ PrintInDestructor () invoked
Catched int exception: 1
ThrowInDestructor stack unwinding
~ ThrowInDestructor () stack unwinding, not throwing
~ PrintInDestructor () invoked
Catched const char * exception: BANG! 3r33578. 3r3-3579. 3r3-3598.  3r3615. 3r3600. Please note that the function
std :: uncaught_exception ()
is
deprecated
starting with C ++ Standard 1? therefore, in order to compile an example, the corresponding Vorning has to be suppressed (cf. 3r3-3601. repository with examples from article 3r3-3602.).
3r3-3598.  3r3615. 3r3600. The problem with this function is that it checks whether we are in the process of spin stack promotion by exception. But it’s impossible to understand whether the current destructor was called during the stack promotion. As a result, if stack spinup occurs, but the destructor of some object is called normally, 3r3r91. std :: uncaught_exception ()
will still return
true
.
3r3-3598.  3r3615. 3r? 3572. 3r33573. class MayThrowInDestructor {
3r3615. public: 3r315615. ~ MayThrowInDestructor () noexcept (false) {
if (std :: uncaught_exception ()) {
std :: cerr "~ MayThrowInDestructor () stack unwinding, not throwingn"; 3r3615.} else {
std :: cerr "~ MayThrowInDestructor () normal case, throwingn"; 3r3615. throw_int_func (); 3r3615.} 3r33615.} 3r33615. 3r3615.}; 3r3615. 3r3615. class ThrowCatchInDestructor {
3r3615. public: 3r315615. ~ ThrowCatchInDestructor () noexcept (false) {
try {3r31515. MayThrowInDestructor may_throw; 3r3615.} catch (int i) {
std :: cerr "Catched int in ~ ThrowCatchInDestructor ():" i "n"; 3r3615.} 3r33615.} 3r33615. 3r3615. private: 3r31536. PrintInDestructor member_; 3r3615. 3r3615.}; 3r3615. 3r3615. int main (int, char **) {
3r3615. try {3r31515. ThrowCatchInDestructor stack_unwind; 3r3615. std :: cerr "ThrowInDestructor stack unwindingn"; 3r3615. throw "BANG!"; 3r3615.} catch (int i) {
std :: cerr "Catched int exception:" i "n"; 3r3615.} catch (const char * s) {3r336155. std :: cerr "Catched const char * exception:" s "n"; 3r3615.} catch () {
std :: cerr "Catched unknown exceptionn"; 3r3615.} 3r33615. 3r3615. return 0; 3r3615. 3r3615.} 3r3-3579. 3r3-3598.  3r3615. 3r3600. Result: 3r3603. 3r3-3598.  3r3615. 3r? 3572. 3r33573. ThrowInDestructor stack unwinding 3r3-3615. ~ MayThrowInDestructor () stack unwinding, not throwing
~ PrintInDestructor () invoked
Catched const char * exception: BANG! 3r33578. 3r3-3579. 3r3-3598.  3r3615. 3r3600. In the new Standard C ++ 17 for the replacement 3r33591. std :: uncaught_exception ()
Function 3r3-39191 was introduced. std :: uncaught_exceptions ()
(note the plural), which instead of the boolean value returns the number of currently active exceptions (here’s a detailed 3-333390. justification 3r3602.).
3r3-3598.  3r3615. 3r3600. This is how the problem described above is solved with the help of 3r33591. std :: uncaught_exceptions ()
:
3r3-3598.  3r3615. 3r? 3572. 3r33573. class MayThrowInDestructor {
3r3615. public: 3r315615. MayThrowInDestructor (): exceptions_ (std :: uncaught_exceptions ()) {}
~ MayThrowInDestructor () noexcept (false) {
if (std :: uncaught_exceptions ()> exceptions_) {
std :: cerr "~ MayThrowInDestructor () stack unwinding, not throwingn"; 3r3615.} else {
std :: cerr "~ MayThrowInDestructor () normal case, throwingn"; 3r3615. throw_int_func (); 3r3615.} 3r33615.} 3r33615. 3r3615. private: 3r31536. int exceptions_; 3r3615. 3r3615.}; 3r3615. 3r3615. class ThrowCatchInDestructor {
3r3615. public: 3r315615. ~ ThrowCatchInDestructor () noexcept (false) {
try {3r31515. MayThrowInDestructor may_throw; 3r3615.} catch (int i) {
std :: cerr "Catched int in ~ ThrowCatchInDestructor ():" i "n"; 3r3615.} 3r33615.} 3r33615. 3r3615. private: 3r31536. PrintInDestructor member_; 3r3615. 3r3615.}; 3r3615. 3r3615. int main (int, char **) {
3r3615. try {3r31515. ThrowCatchInDestructor stack_unwind; 3r3615. std :: cerr "ThrowInDestructor stack unwindingn"; 3r3615. throw "BANG!"; 3r3615.} catch (int i) {
std :: cerr "Catched int exception:" i "n"; 3r3615.} catch (const char * s) {3r336155. std :: cerr "Catched const char * exception:" s "n"; 3r3615.} catch () {
std :: cerr "Catched unknown exceptionn"; 3r3615.} 3r33615. 3r3615. return 0; 3r3615. 3r3615.} 3r3-3579. 3r3-3598.  3r3615. 3r3600. Result: 3r3603. 3r3-3598.  3r3615. 3r? 3572. 3r33573. ThrowInDestructor stack unwinding
~ MayThrowInDestructor () normal case, throwing
throw_int_func () invoked
Catched int in ~ ThrowCatchInDestructor (): 1
~ PrintInDestructor () invoked
Catched const char * exception: BANG! 3r33578. 3r3-3579. 3r3-3598.  3r3615.
When you really, really want to throw out a few exceptions at once 3r33597. 3r3-3598.  3r3615. 3r3600. 3r3-3591. std :: uncaught_exceptions ()
avoids calling 3r33591. std :: terminate ()
but does not help correctly handle multiple exceptions. Ideally, I would like to have a mechanism that allows you to save all the exceptions thrown out, and then process them in one place.
3r3-3598.  3r3615.
I want to remind once again that the mechanism proposed by me below serves only to demonstrate the concept and is not recommended for use in a real industrial code.
3r3600. The essence of the idea is to catch exceptions and store them in a container, and then one by one get and process. In order to save exception objects, in C ++ there is a special type
std :: exception_ptr
. The type structure in the Standard is not disclosed, but it is said that it is in its essence 3r-3591. shared_ptr
to the object of the exception.
3r3-3598.  3r3615. 3r3600. How then to process these exceptions? For this there is a function
std :: rethrow_exception ()
which accepts a pointer
std :: exception_ptr
and throws the corresponding exception. We only need to catch it with the appropriate catch-section and process it, after which we can proceed to the next exception object.
3r3-3598.  3r3615. 3r? 3572. 3r33573. using exceptions_queue = std :: stack
; 3r3615. 3r3615. //Get exceptions queue for current thread
exceptions_queue & get_queue () {
thread_local exceptions_queue queue_; 3r3615. return queue_; 3r3615.} 3r33615. 3r3615. //Invoke functor and save exception in queue
void safe_invoke (std :: function
f) noexcept {
try {3r31515. f (); 3r3615.} catch () {
get_queue (). push (std :: current_exception ()); 3r3615.} 3r33615.} 3r33615. 3r3615. class ThrowInDestructor {
3r3615. public: 3r315615. ~ ThrowInDestructor () noexcept {
std :: cerr "~ ThrowInDestructor () invokedn"; 3r3615. safe_invoke ([]() {3r-33615. throw_int_func (); 3r?63615.}); 3r3615.} 3r33615. 3r3615. private: 3r31536. PrintInDestructor member_; 3r3615. 3r3615.}; 3r3615. 3r3615. int main (int, char **) {
3r3615. safe_invoke ([]() {3r3-33615. ThrowInDestructor bad;
throw "BANG!"; 3r3361515.}); 3r3615. 3r3615. auto & q = get_queue (); 3r3615. while (! q.empty ()) {3r-33615. try {3r31515. std :: exception_ptr ex = q.top (); 3r3615. q.pop (); 3r3615. if (ex! = nullptr) {3r33615. std :: rethrow_exception (ex); 3r3615.} 3r33615.} catch (int i) {
std :: cerr "Catched int exception:" i "n"; 3r3615.} catch (const char * s) {3r336155. std :: cerr "Catched const char * exception:" s "n"; 3r3615.} catch () {
std :: cerr "Catched unknown exceptionn"; 3r3615.} 3r33615.} 3r33615. 3r3615. return 0; 3r3615. 3r3615.} 3r3-3579. 3r3-3598.  3r3615. 3r3600. Result: 3r3603. 3r3-3598.  3r3615. 3r? 3572. 3r33573. ~ ThrowInDestructor () invoked
throw_int_func () invoked
~ PrintInDestructor () invoked
Catched const char * exception: BANG! 3r3615. Catched int exception: 1 3r3-3579. 3r3-3598.  3r3615. 3r3600. In the example above, the stack is used to save objects of exceptions, however, exception handling will be performed according to the FIFO principle (that is, logically this is the queue — the exception thrown first will be processed first).
3r3-3598.  3r3615. 3r33586. Conclusions 3r39797. 3r3-3598.  3r3615. 3r3600. Throwing exceptions in the destructors of objects is indeed a bad idea, and in any new code I strongly recommend not to do this, declaring destructors 3r-39191. noexcept
. However, with the support and debugging of legacy code, it may be necessary to correctly handle exceptions thrown from destructors, including during stack promotion, and modern C ++ provides us with mechanisms for this. I hope the ideas presented in the article will help you on this difficult path.
3r3-3598.  3r3615. 3r3-3596. References
3r3-3598.  3r3615. 3r3600. 3r3601. Repository with examples from article 3r3602.
3r3611. 3r3615. 3r3615. 3r3615. 3r3608. ! function (e) {function t (t, n) {if (! (n in e)) {for (var r, a = e.document, i = a.scripts, o = i.length; o-- ;) if (-1! == i[o].src.indexOf (t)) {r = i[o]; break} if (! r) {r = a.createElement ("script"), r.type = "text /jаvascript", r.async =! ? r.defer =! ? r.src = t, r.charset = "UTF-8"; var d = function () {var e = a.getElementsByTagName ("script")[0]; e.parentNode.insertBefore (r, e)}; "[object Opera]" == e.opera? a.addEventListener? a.addEventListener ("DOMContentLoaded", d,! 1): e.attachEvent ("onload", d ): d ()}}} t ("//mediator.mail.ru/script/2820404/"""_mediator") () (); 3r3609. 3r3615. 3r3611. 3r3615. 3r3615. 3r3615. 3r3615.
+ 0 -

Add comment