Are destructors called after a throw in C++?
Asked Answered
T

3

66

I ran a sample program and indeed destructors for stack-allocated objects are called, but is this guaranteed by the standard?

Translucent answered 29/11, 2011 at 13:23 Comment(3)
Sure it is. RAII, which is one of the most important idioms in C++, depends on this.Thorn
Yes, that's the whole point of exception handling.Chaing
@Thorn & Kerrek SB, If the exception is not caught, stack unwinding is not guaranteed to happen, it is implementation defined: see NPE's answer below, the last part is the quote from the standard that says this.Lithium
T
88

Yes, it is guaranteed (provided the exception is caught), down to the order in which the destructors are invoked:

C++11 15.2 Constructors and destructors [except.ctor]

1 As control passes from a throw-expression to a handler, destructors are invoked for all automatic objects constructed since the try block was entered. The automatic objects are destroyed in the reverse order of the completion of their construction.

Furthermore, if the exception is thrown during object construction, the subobjects of the partially-constructed object are guaranteed to be correctly destroyed:

2 An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s destructor will be invoked. If the object was allocated in a new-expression, the matching deallocation function (3.7.4.2, 5.3.4, 12.5), if any, is called to free the storage occupied by the object.

This whole process is known as "stack unwinding":

3 The process of calling destructors for automatic objects constructed on the path from a try block to a throw-expression is called “stack unwinding.” If a destructor called during stack unwinding exits with an exception, std::terminate is called (15.5.1).

Stack unwinding forms the basis of the widely-used technique called Resource Acquisition Is Initialization (RAII).

Note that stack unwinding is not necessarily done if the exception is not caught. In this case it's up to the implementation whether stack unwinding is done. But whether stack unwinding is done or not, in this case you're guaranteed a final call to std::terminate.

C++11 15.5.1 The std::terminate() function [except.terminate]

2 … In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called.

Townsend answered 29/11, 2011 at 13:24 Comment(3)
Note: regarding the construction of an object that is interrupted. The object itself is not destroyed (it never actually lived), what is guaranteed is that the subparts (base classes, attributes) that had been fully constructed so far will be destroyed in the reverse order.Whoops
Added information about stack unwinding (or not) for uncaught exception.Heideheidegger
What if the exception isn't caught?Orvie
U
10

Yes, destructors are guaranteed to be called on stack unwinding, including unwinding due to exception being thrown. There are only few tricks with exceptions that you have to remember:

  • Destructor of the class is not called if exception is thrown in its constructor.
  • Exception is automatically re-thrown if caught in construction initialization list catch block.
Upstate answered 29/11, 2011 at 13:29 Comment(6)
3) Destructors should never throw exceptions, as there is no way to handle them adequately.Tsuda
@Tsuda some counter-examples do exist.Heideheidegger
@AlfP.Steinbach: Any destructor thrown during stack-unwinding (due to another exception being thrown) will terminate() your process. I'd be interested in seeing counter-examples...Tsuda
@DevSolar: you're (intentionally?) unclear on what you'd like counter examples to. but regarding the first claim, that "destructors should never throw exceptions", a not totally uncommon counter-example is an object that represents a function result, and that throws from its destructor if the caller code has not checked whether it represents a failure. Another example is a transaction guard object that throws from its destructor unless the code in which it's embedded, has succeeded in its endeavors (to e.g. transfer ownership of something) and called its release method.Heideheidegger
the problem is, as you note, that throwing from a destructor can be pretty fatal if there is an unhandled exception (not stack unwinding, since you can have a try executed in a destructor). visual c++ never implemented the standard's function to check, and that function is anyway not adequate. so, it's a bit problematic, but not a total showstopper, since the usage code can be designed around it.Heideheidegger
@AlfP.Steinbach: "Usage code can be designed around it." - I agree. However, rule of thumb is that your objects might be used in a context you did not foresee, and cannot cater for proactively. And if you put destructor-throwing objects in an STL vector, for example, and that vector gets destroyed due to some unrelated exception stack-unwinding, it will call your object's destructor, and your application goes "poof". Sure I can pull the pin off a grenade, handle it carefully for a while and put the pin back in. But I never should do that...Tsuda
B
3

If a throw is caught then normally cpp operations continue. This include destructors and stack popping. However if the exception is not caught, stack popping is not guaranteed.

Also a bare throw or empty throw cannot be caught by my mobile compiler.

example:

#include <Jav/report.h>

int main()
{
 try { throw; }
 catch(...) { rep("I bet this is not caught"); }
 }
Brute answered 19/6, 2019 at 3:2 Comment(2)
I gave this a plus one. Not only is it not caught, but destructors for automatic objects in the try block (easy enough to stick one in) are not invoked (g++ 7.4.0/clang++ 6.0.0 ubuntu), -std=c++[11|14|17]. Doesn't seem to help to use noexcept with main declaration. I also tried pertinent man page options. I can get a warning for a bare throw in a catch block, but not in a try block. If I'm overlooking something, please enlighten.Becki
@Becki Thanks for your plus one. I hope you havent overlooked anything as that is above my pay grade. Even now. Peace out.Brute

© 2022 - 2024 — McMap. All rights reserved.