Destructors that throw

Have you already heard that your destructors had better not throw exceptions? You probably did, because this is what nearly every C++ programmer is taught. But because it is so obvious, it may have escaped your notice that C++, and especially C++11, has well-defined and interesting rules for dealing with throwing destructors. I am not trying to encourage you to throw from destructors. There are good reasons why we are taught not to do it. But since some people do throw from destructors, and they have reasons to do it, and even you might be one of them — inadvertently — it is worth learning what is going on in such case.

I assume that you already know the basic things: that STL components, like containers, or even function std::swap, loose all their exception-safety guarantees if instantiated with throwing-destructor types; and that throwing destructors on some occasions cause a double-exception situation that triggers a call to std::terminate. I start from there to describe less familiar things.

When your destructor throws…

Let’s see what happens in the program when your destructor throws. We will focus on automatic objects. First, assuming that stack unwinding is not in progress — this will be the case most of the time. We already know that if an exception is thrown in a given scope, all destructors of automatic objects defined in this scope are called in the correct order (of course, there are critical situations where even the destructors are not called), and the scope is left. In this respect, a throw from destructor is no different than a throw from a regular function. If you leave your scope normally (no exception thrown yet) and one of your destructors throws, the remaining destructors are still called, and then your exception is propagated further.

Now, for a slightly more interesting situation, if you throw an exception from your destructor’s body, before the exception is let out the destructors of all sub-objects (non-static data members and base classes) are still called. Somewhat similarly, if in your destructor (which is calling sub-objects’ destructors) one of the sub-objects’ destructor throws, the remaining sub-objects are still destroyed before your master destructor passes the exception up. In other words, destructors for all your object’s sub-objects are called regardless if one of them exits via exception.

Knowing that destruction is guaranteed for all sub-objects, even if one of them throws, and knowing that elements of an array are its sub-objects, the next observation should come as no surprise. When we destroy an automatic array, destructors for elements with higher indexes called first, when one of elements’ destructor throws, destructors for remaining elements with lower indexes are still called before exception is propagated further.

Even more interestingly, although it is C++11-specific (C++03 is not clear about this behavior), if you allocate an object (including an array) using operator new and later call operator delete to call the destructor and free memory, if destructor of your object throws, the deallocation function is still called preventing memory leak. (Of course, we all know that calling delete in our code is too low-level and typically indicates poor design and memory leaks, but I tell this just to show you the rules of throwing destructors).

Well, all the above is a bit un-true. It would be true if destructors were freely allowed to throw. It is the case in C++03, but not entirely in C++11. In C++11, a destructor is implicitly specified as noexcept. Even if you add no exception specification and define your destructor like this:

class MyType {
    public: ~MyType() { throw Exception(); }
    // ...
};

The compiler will still invisibly add specification noexcept to your destructor. And this means that the moment your destructor throws an exception, std::terminate will be called, even if there was no double-exception situation. If you are really determined to allow your destructors to throw, you will have to specify this explicitly; you have three options:

  • Explicitly specify your destructor as noexcept(false),
  • Inherit your class from another one that already specifies its destructor as noexcept(false).
  • Put a non-static data member in your class that already specifies its destructor as noexcept(false).

Throwing during stack unwinding

Now let’s answer a slightly different question, assuming that your destructor is allowed to throw, what happens when it throws, but function std::uncaught_exception returns value true at that time. If your answer is, “the program will call std::terminate,” it means that you are well aware of exception handling mechanism in C++, but read on to see why this answer is not always correct.

In C++ the events of stack unwinding can nest. When an exception is thrown, stack unwinding begins, and destructors are called. A destructor, in many aspects, is a normal function; it can create automatic objects (and register their destructors to be called at the end of the scope), call other functions that call other functions: it can “wind the stack” back inside the process of stack unwinding. Until any exception gets out from the destructor we can throw at will inside, even from other destructors, and this does not account for double-exception situation or “throwing from destructor during stack unwinding.” Let’s illustrate it with an example.

# include <exception>
# include <cassert>

struct OuterException : std::exception{};
struct InnerException : std::exception{};


struct LegalThrowingDtor 
{
    ~LegalThrowingDtor() /*noexcept(false)*/ {
        assert( std::uncaught_exception() ); /*5*/
        throw InnerException();
    }
};

struct InnerWorld
{
    ~InnerWorld() {
        try {
            LegalThrowingDtor obj; /*4*/
        }
        catch( OuterException const& ) { /*3*/
            assert( false );
        }
        catch( InnerException const & ) { 
            // swallow
        }
    }
};

int main() 
try {
    InnerWorld innerWorld;
    throw OuterException(); /*1*/
}
catch( OuterException const& ) { /*2*/
    assert( !std::uncaught_exception() );
    return 0;
}

Let’s see what this code does. In function main we create an automatic object innerWorld. Only its destructor is of interest to us. Once we throw OuterException at point /*1*/, function std::uncaught_exception will start returning true and will go back to returning false only when we catch exception of type OuterException at point /*2*/.

Now, during stack unwinding we enter the destructor of innerWorld. We start another try-block, and therewith we sort of pause stack unwinding and start winding it again locally, as if in the nested scope. Note that although we are in a try block, and although there is an active uncaught exception of type OuterException, and although there is a matching handler at point /*3*/, it will not catch our exception, because the catch handler is from the different (nested) level than the exception. A handler will only catch exceptions thrown from within its matching try statement.

Now, to the most interesting part, at point /*4*/ we create an automatic object obj and destroy it the moment after. In this destructor (at point /*5*/) we have a behaviour that some may find surprising. Function std::uncaught_exception returns true; yet, because for the moment we paused unwinding the stack, we can safely throw an exception from destructor, without terminating the program. And thus we have two uncaught exceptions, but not a double-exception situation! Next, the handler for InnerException handles the first exception, and we exit the destructor of innerWorld normally.

This is why it is impossible to automatically detect in destructor if it was called due to a throw from its scope or due to the normal scope exit (some incorrectly try the std::uncaught_exception trick), and this is why it is impossible to implement scope(failure) from language D in C++.

One thing to be noted is that object’s lifetime ends when its destructor is called. It does not matter if destructor succeeds or exits via exception: the object is considered ‘dead’ and accessing it causes undefined behavior. Is it technically even possible to access a destroyed object?

Function-try-blocks

Yes, for instance, in function-try-block in the destructor.

class MyType
{
    Resource res1;
    Resource res2;

    public: 
    ~MyType() try {
    }
    catch( ResourceException const& ex ) {
        (res1); // can access - but causes UB
    }

    // ...
};

A function-try-block in destructor can be used to temporarily catch (but usually not to handle), exceptions thrown by sub-objects’ destructors. Technically, inside the catch clause, sub-object res1 is in scope, but since its destructor has already been called, its lifetime has already ended.

One other important thing to be remembered about catch clauses in function-try-blocks in destructors (and constructors too) is that, unlike in any other catch clause, they do not allow you to stop the stack unwinding. You can do some operations, modify an exception you caught, re-throw it, throw a different one, but you cannot exit the destructor normally. One might be tempted to think that one prevented the exception thrown in sub-object’s destructor from leaving one’s destructor, but even if no throw expression is explicitly stated in the code (as in our example above), the currently handled exception will be re-thrown.

Invisible destructors

Note that there may be destructors that you are using, but you are not aware of, because they are hidden in abstractions such as ScopeGuard, or its variant, Boost’s ScopeExit.

For an another example of such invisible destructor, consider a function call wrapper described here. The idea, in short, is that we want a wrapper class template Wrap for any type T that for every call to any member function T::f will call B() just before f is executed, and will call A() just after f is executed. This makes sense if you want B to be a lock and A to be the unlock on a given mutex. Such wrapper is implementable but requires calling A in the destructor of an auxiliary object whose life time terminates at the end of instruction.

Every no-throwing rules that apply to destructors, and best practices you wish to apply to destructors, apply equally to those and similar libraries that utilize destructors in clever ways.

Also note that destructors of temporary objects are scheduled to be run after the full expression they were created in is executed. This might create surprising effects. Consider the following code.

static_assert( noexcept(a * a + b * b), "expr must not throw" );
a * a + b * b;

Even though the assertion evaluates to true at compile-time, the expression below may still throw an exception at run-time; because the expression creates three temporaries, execution of destructors of those temporaries is not checked in the assertion, and any of these destructors could throw (unless they are declared as noexcept).

Destructors that terminate program

When switching from C++03 to C++11, your destructors (which were allowed to throw exceptions) will now silently acquire a noexcept specification. (In order to prevent that, you need to either explicitly declare your destructor as noexcept(false) or derive from a user-defined type with noexcept(false)-destructor or have a non-static data member of user-defined type with noexcept(false)-destructor). This means that if you used to throw from destructors inadvertently, you will be now calling std::terminate inadvertently. Throw from destructor inadvertently? Yes. Do you use a log in your application? Do you log destruction of objects, like this?

MyType::~MyType()
{
    LOG( info, "destroying object" );
}

Even if your logging library swallows all failures (logging may fail after all), does LOG take its arguments by value or by reference? A Reference to type std::string? If you log, do you try to wrap the logging with a catch-all handler that will swallow an exception? Even if you write code like the above you might have never noticed the ‘problem’, because even though the logging instruction might throw, it just does not.

For the end, note that while no destructor in C++ Standard Library is allowed to throw exceptions, destructors are allowed to std::terminate the program in case of an error. This is the case for std::thread. If you create an automatic object of class std::thread in order to launch an asynchronous computation, and you leave the scope (trigger the destructor call) without having joined with or detached from the other thread, std::terminate will be called. Note that even if your intention is to join with the other thread it might be prevented by an exception throw. Consider:

void parallelRun(function<void()> task1, function<void()> task2)
{
    std::thread th{ task1 }; // (new initialization syntax)
    task2();   // throws exception!
    th.join(); // join omitted
}              // terminate!

Therefore, the advice for you regarding threads is, do not use such low-level abstraction as thread — really. Use std::async instead:

void parallelRun(function<void()> task1, function<void()> task2)
{
    auto future = std::async( task1 );
    task2();       // throws exception
    future.wait(); // explicit wait skipped
}                  // will wait in destructor

Further reading

  1. Boris Kolpackov, “Throwing Destructors.”
  2. Jon Kalb, Dave Abrahams, “Evil, or Just Misunderstood?
This entry was posted in programming and tagged , , , , . Bookmark the permalink.

16 Responses to Destructors that throw

  1. Faisal Vali says:

    Excellent post! I don’t think i really understood (or had ever put forth real effort to understand) this space (throwing exceptions within destructors) – but thanks to you, I think i am better equipped to appreciate the complexities of this terrain! Thank you!

  2. dribeas says:

    If you are really determined to allow your destructors to throw there is additionally a third option: add a member attribute whose destructor has a throw specification different than `noexcept(true)`.

    Regarding types with destructors that can throw, it is important to note that such types cannot be managed through `unique_ptr` or `shared_ptr`, as the requirements that the library imposes on the deleters of those two smart pointers require that a call to the deleter with the stored pointer will not throw, or safely in any container as during destruction of the container the stored objects will be destroyed and that will in turn violate the implicit exception specification of the container’s destructor (or `clear()`, although you technically you can empty the container by calling `erase`)

    Basically types with throwing destructors are a second class citizens in C++11 if not “dangerous materials” that must be handled with care and a full protective suit.

  3. Sten says:

    The section Function-try-blocks is not completely true. If you do an explicit ”return” inside the “catch” block, the exception is not rethrown.

  4. Dmitry says:

    Interesting morning reading.
    BTW You wrote that destructors in C++11 got implicit noexcept(true) but I only found 15.4.15 which talks about deallocation functions. Thus stack allocated objects can still throw from their destructors (dribeas wrote above about consequences).

    • Hi Dimitry. The Standard also defines a similar requirement for destructors, although in a two-step manner:

      15.4/14:

      An implicitly declared special member function shall have an exception-specification. If f is an implicitly declared […] destructor […], its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f’s implicit definition; f shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions.

      12.4/3:

      A declaration of a destructor that does not have an exception-specification is implicitly considered to have the same exception-specification as an implicit declaration.

  5. Pingback: C++ Daily 2015-08-24 | blog.vladon.com

  6. myrussia2001 says:

    just checked array destruction in gcc
    and it doesn’t behave as described in regrad to exceptions
    it just stops array destructing in the middle
    now the interesting question is if that’s correct or wrong behavior

  7. myrussia2001 says:

    I’ve found an open bug in GCC’s Bugzilla related to my previous comment about arrays.
    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66451

  8. Pingback: c++low latency IV (MS-shanghai?) – keep learning 活到老学到老

  9. Pingback: throw exception from dtor: justified use case@@ – keep learning 活到老学到老

  10. Pingback: We shouldn’t throw exceptions in C++ destructors, but what to do if it does happen? https://akrzemi1.wordpress.com/2011/09/21/destructors-that-throw/ | Random Thoughts

  11. Pingback: Everything you should know about the dynamic_cast and typeid tools. | C++ - don't panic!

  12. Pingback: Missing destructor in Visual Studio? - Tutorial Guruji

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.