Please consider the following example:
#include <csignal>
class A
{
public:
virtual ~A() {}
virtual void foo() = 0;
};
class B : public A
{
public:
virtual ~B() { throw 5; }
virtual void foo() {}
};
int main(int, char * [])
{
A * b = new B();
try
{
delete b;
}
catch ( ... )
{
raise(SIGTRAP);
}
return 0;
}
I've always thought (naive me) that when the program gets in this case, into catch
section, then object B
at which b
points will be intact because it's quite logical that the exception will have "cancelled" (if programmed safely) the effect of destructor. But when I tried to run this snippet in gdb and got to the breakpoint in catch
section I saw that B object was gone and only A base object left because the vtable looked like this:
(gdb) i vtbl b
vtable for 'A' @ 0x400cf0 (subobject @ 0x603010):
[0]: 0x0
[1]: 0x0
[2]: 0x4008e0 <__cxa_pure_virtual@plt>
My question: is there a way to avoid (half-)destruction of the vtable if I passionately want to throw an exception from a destructor?
Throwing an exception out of a destructor is dangerous. If another exception is already propagating the application will terminate.
When an exception is thrown from a constructor, the object is not considered instantiated, and therefore its destructor will not be called. But all destructors of already successfully constructed base and member objects of the same master object will be called.
When an exception is thrown, destructors of the objects (whose scope ends with the try block) are automatically called before the catch block gets executed. That is why the above program prints “Destructing an object of Test” before “Caught 10“.
I also know that the following cannot throw exceptions either: Destructors. Reading/writing primitive types.
I've always thought (naive me) that when the program gets in this case, into catch section, then object B at which b points will be intact because it's quite logical that the exception will have "cancelled" (if programmed safely) the effect of destructor.
This is not true. The standard says:
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.
(15.2/2 in N4140)
and, probably more importantly:
The lifetime of an object of type T ends when:
— if T is a class type with a non-trivial destructor (12.4), the destructor call starts
(3.8/1.3 in N4140)
As every member and base of b
is completely constructed and non of their destructors where entered yet they will be considered destroyed. So in your catch
block, the entire object b
points to is already dead.
The rational behind this is probably forbidding "half destructed" objects, as it is unclear what the state of an object that cannot be destroyed should be. For instance, what if only some of the members where destroyed already?
Even the standard itself advises against exceptions leaving destructors. As I wrote in a comment earlier, throwing destructors are weird.
One good rule we can take from the quote above: An object starts to exist when its constructor is done without throwing, and it ceases to exist as soon as its destructor begins execution, regardless of how it exits. (This is restated more clearly at other places in the standard. There are exceptions to this, don't care about them.)
So in conclusion:
is there a way to avoid (half-)destruction of the vtable if I passionately want to throw an exception from a destructor?
No. As soon as you enter the destructor, your object is done for.
when the program gets in this case, into catch section, then object B at which b points will be intact because it's quite logical that the exception will have "cancelled" (if programmed safely) the effect of destructor.
No. The lifetime of an object ends when its destructor starts.
You cannot cancel a destructor.
As others said, throwing destructors in C++ are weird and you want to avoid them except for special cases.
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