2

I did an experiment with throwing an exception from a destructor and got a result that I did not expect. See the code snippet:

#include <iostream>
#include <exception>

class A
{
public:

    ~A() noexcept(false)
    {
        std::cout << "in ~A" << std::endl;
        //throw std::runtime_error("~A exception");
    }
};

class M
{
public:

    ~M() noexcept(false)
    {
        std::cout << "in ~M" << std::endl;
        //throw std::runtime_error("~M exception");
    }
};

class B : public A
{
public:

    ~B() noexcept(false)
    {
        std::cout << "in ~B" << std::endl;
        throw std::runtime_error("~B exception");
    }
    
private:

    M m;
};

class X
{
public:

    ~X() noexcept(false)
    {
        std::cout << "in ~X" << std::endl;
        //throw std::runtime_error("~X exception");
    }
};

int main()
{
    try
    {
        X x;
        B b;
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what() << '\n';
    }
    
    return 0;
}

the output:

in ~B
in ~M
in ~A
in ~X
~B exception
0

so the question is: is this correct that if I throw an exception from a destructor it is caught then all the subsequent destructors (including the destructors of the base class, class members and the destructors of the objects declared on the stack (as X, for example)) are called and then the exception is rethrown?

EDIT1:

The same result if I modify main() as follows:

int main()
{
    try
    {
        X x;
        B * b = new B();
        delete b;
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what() << '\n';
    }
    
    return 0;
}
Dmitriano
  • 1,878
  • 13
  • 29
  • Not an answer, but [this older question](https://stackoverflow.com/questions/130117/throwing-exceptions-out-of-a-destructor) contains a longer discussion on the topic. – Nelewout Jun 05 '21 at 16:01
  • 1
    Does this answer your question? [throwing exceptions out of a destructor](https://stackoverflow.com/questions/130117/throwing-exceptions-out-of-a-destructor) – Linear Data Structure Jun 05 '21 at 16:22
  • 1
    Of course this is a legitimate question, but just in case I'll put here this cpp core [guideline](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c36-a-destructor-must-not-fail) *C.36: A destructor must not fail* – MatG Jun 05 '21 at 16:27
  • 1
    Good question. I thought (and I may be mistaken) that just after the throw the exception mechanism then locates the catch, and then unwinds the stack (which calls the destructors) to that catch, then the exception is caught. Not part of the question, but... if during the unwind another destructor also throws an exception (so there would be two of them in flight, which won't work) the exception mechanism terminates the application. – Eljay Jun 05 '21 at 17:03

1 Answers1

2

and then the exception is rethrown?

No, it is not "rethrown". The destructors are being called as part of throwing (and catching) the exception. This is slightly a semantical difference, but an exception gets thrown only once, here, and the objects get destroyed as part of the exception getting thrown.

Let's set aside the issue of throwing exceptions from destructors (which is, in itself, an iffy topic). Consider the following code:

void function()
{
    X some_object;

    throw std::runtime_error("bad");
}

X's destructor gets called here as part of throwing the exception. Why? Because the object is getting destroyed. The destructor gets called no differently from what it gets called if this function returns normally. The only difference is that instead of returning to the caller, the execution returns to wherever the exception gets caught (presuming that it gets caught). But, either way, execution leaves this scope, which means that the automatically-scoped object must get destroyed, which means that its destructor must get called.

A destructor of an automatically-scoped object gets called whenever execution leaves the object's automatic scope. Whether the scope is left voluntarily, or involuntarily due to a thrown exception, makes no difference.

In your case, this is no different. In fact, your code already entered the most derived-object's destructor as part of destroying it normally (via delete or by leaving the automatic object's scope). The fact that an exception gets thrown does not really change, much, the fact that the objects get destroyed. The only difference is that the object get destroyed as part of throwing an exception, since the execution moves where it gets caught, but whether the execution moves to where it gets returned or, or where its exception gets caught, makes no difference with regards to the destructor invocation.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • If the exception is *not* caught ("Up and Out!" —Willy Wonka), I'm not seeing the destructors being called at all. I'm just seeing the `std::terminate`. But that may be an implementation detail. – Eljay Jun 05 '21 at 20:03
  • 1
    @Eljay -- when an exception is thrown but not caught it's implementation defined whether the stack gets unwound before calling `std:terminate`. – Pete Becker Jun 05 '21 at 20:07