When the try-block encounters an exception, the stack is unwound. If an object was created inside the try-block, the destructor is called. If the destructor throws another exception, this exception is not caught and the program is terminated.
So if you have:
struct A {
~A () noexcept(false) {
std::cout << "A::~A" << std::endl;
throw std::runtime_error("A::~A ERROR");
}
};
And then your try-catch block is something like:
try {
A a1;
A a2;
} catch (...) {}
Then when the try-block finishes, the destructor for a2
throws, the exception is caught, then the destructor for a1
throws and the program is terminated. Everything works as expected.
But if you introduce another struct that also throws in the destructor, but inherits from A
or has an instance of A
as a member, things become confusing. For example, if you have:
struct B : A {
~B () noexcept(false) {
std::cout << "B::~B" << std::endl;
throw std::runtime_error("B::~B ERROR");
}
};
Then if you do:
try {
B b;
A a;
} catch (...) {}
The expected outcome should be that A::~A
is called an the exception is caught, then B::~B
is called an the program terminates. But instead, in all compilers I tried except MSVC, the output is:
A::~A
B::~B
A::~A
terminate called after throwing an instance of std::runtime_error
what(): A::~A ERROR
As if two exceptions were caught and the third terminated the program.
The same also happens if you define B
as:
struct B {
~B () noexcept(false) {
std::cout << "B::~B" << std::endl;
throw std::runtime_error("B::~B ERROR");
}
A a;
};
I also tried some other combinations with more structs.
Don't bother putting anything in the catch-block, because the program will never even go there.
And yes, I know that ideally destructors shouldn't even throw exceptions. It's more of a curiosity after having read an article about throwing destructors.
Throwing an exception out of a destructor is dangerous. If another exception is already propagating the application will terminate.
The C++ rule is that you must never throw an exception from a destructor that is being called during the "stack unwinding" process of another exception. For example, if someone says throw Foo(), the stack will be unwound so all the stack frames between the throw Foo() and the } catch (Foo e) { will get popped.
If a failure occurs during destruction of an object and the object cannot handle the failure itself, then throwing is the only option. It is the responsibility of the programmer to catch the exception at a point at which the failure can be handled properly.
When an exception is thrown and control passes from a try block to a handler, the C++ run time calls destructors for all automatic objects constructed since the beginning of the try block. This process is called stack unwinding. The automatic objects are destroyed in reverse order of their construction.
I think the behavior you are observing is implementation-dependent. From the c++ reference on std::terminate() (emphasis mine):
std::terminate() is called by the C++ runtime when exception handling fails for any of the following reasons:
1) an exception is thrown and not caught (it is implementation-defined whether any stack unwinding is done in this case)
In your first case, on exiting the scope:
A
is invoked.std::runtime_error("A::~A ERROR")
is thrown.catch(...)
.B
is called.At this point the std::terminate()
is called. But it is implementation-defined whether
a) the program immediately terminates and gives the output you expect
or
b) the program unwinds the stack, hence calls the destructor of the base class A
and then terminates; this is what you see observe.
See the code live on Coliru
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With