17

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?

edmz
  • 8,220
  • 2
  • 26
  • 45
krokoziabla
  • 695
  • 1
  • 6
  • 21
  • 6
    Throwing destructors are weird, avoid that if possible. – Baum mit Augen Nov 30 '15 at 20:22
  • Throwing destructors are worse than weird. They are actually [quite dangerous](https://isocpp.org/wiki/faq/exceptions#dtors-shouldnt-throw) and should be avoided in nearly all situations. – Kevin Nov 30 '15 at 23:48
  • Why would you want to abort the destruction of an object? Do you know that leaving a scope means, destruction of local objects? So do you want the compiler to jump up the stack to avoid destroying your object? Did you ever notice, that functions for cleaning up resources, are usually always successful -- and return void. –  Jan 12 '16 at 17:01

3 Answers3

17

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.

Community
  • 1
  • 1
Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
6

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.

Community
  • 1
  • 1
Martin Ba
  • 37,187
  • 33
  • 183
  • 337
  • It looks like I have a special case. In my real program B object is a V4L2 frame wrapper and in its destructor I make an attempt to queue it back to the driver and in case that fails I have hoped to defer destroying the wrapper until the next try. – krokoziabla Nov 30 '15 at 21:06
  • @krokoziabla: In that case, I would suggest that what you need is to have a wrapper which holds a pointer to the object of interest and which, rather than cleaning up objects directly, moves the pointer into a queue of objects awaiting cleanup. Objects in the queue could then be processed in a context that would be better able to deal with exceptions thrown thereby. C++ all but requires that types be defined in such a way that destructors cannot possibly fail, but if the destructor isn't responsible for actually performing cleanup but merely for arranging for it to get done... – supercat Nov 30 '15 at 21:24
  • ...then the constructor's ability to satisfy its obligations wouldn't depend upon the object's ability to actually perform the required cleanup. – supercat Nov 30 '15 at 21:26
  • Yeah, I had an idea about using a garbage collector for this but the solution where the destructor queues the frame seemed so elegant to me because the destructor would do the job completely opposite to that of the constructor which dequeued them. Well, life is life. – krokoziabla Nov 30 '15 at 21:30
  • 1
    @krokoziabla Sounds like your class has two different responsibilities. Separate those responsibilities and this gets more straightforward. – Andre Kostur Dec 01 '15 at 01:10
0

It's well defined and safe to throw from a destructor as far as that instance is concerned. Where you start running into problems is in arrays (because it can't finish deleting the array and you have no way of getting it back) and catch clauses (can end up calling terminate). It's also difficult (I think it's actually impossible, but not ready to claim that from memory) to write exception safe code if destructors throw.

I've used throwing destructors to do things though. For example I was working with an API that might return an error code and allocate an error blob. I wrote a little scope guard thing that would hand out references to put that data in, and check in the destructor for error conditions. If it saw one it would convert it into an exception and throw it.

Constructs like this are technically safe, but you kinda want to avoid it until you know what you're doing. You have to make it clear that these things cannot be stored in vectors or arrays and might render exception safe code unsafe. The main issue is that pretty much everyone expects all destructors to be nothrow.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125