Why can't you cast the destructor. example

I've read that it's nice to throw from destructor due to stack unwrapping. I'm not sure I fully understand this. So I tried the following example

struct foo
{
    ~foo()
    {
        throw 1;
    }
};


struct bar
{
    ~bar()
    {
        throw 2;
    }
};

int main()
{
    try 
    {
        foo a;
        bar b;
        throw 3;
    }catch(int a)
    {
        std::cout << a;
    }
}

      

Now I was expecting a to be 1, because the first 3 will be selected, then the destructor of b will be called, which yields 2, and then the destructor of a will be called, which throws 1. Apparently this is not the case, and this may explain why not throw out destructors. My question is, why was the () called destructor b canceled?

+3


source to share


4 answers


Whenever you throw an exception while handling exceptions, you get a special exception that cannot be caught and this results in an interrupt.



You can use std::uncaught_exception

to determine if exception handling is in progress and not thrown in this case.

+3


source


Throwing an exception during stack removal will cause something to be called std::terminate

, whose default action is to invoke std::abort

.

CERT has a good explanation in ERR33-CPP. Destructors shouldn't throw exceptions in the doc that says (emphasis mine):

The destructor is likely to be called during stack unwinding as a result of an exception. If the destructor itself throws an exception thrown as a result of an exception being thrown, then the std :: terminate () function is called with the default effect of calling std :: abort () . This can provide an opportunity for a denial of service attack. Therefore, destructors must satisfy the requirement without throwing, that is, they must not throw an exception if they themselves were thrown as a result of an exception.

This is described in the standard C ++ project section 15.2

Constructors and Destructors, which states:

The process of calling destructors for constructed automatic objects on the way from a try block to a throw statement is called β€œstack unwinding.” If a destructor called during stack unwinding exits with an exception, std :: terminate is called (15.5.1). [Note: so destructors usually have to catch exceptions and prevent them from propagating out of the destructor. -end note]

Note that in C ++ 11 destructors are set implicitly noexcept(true)

as long as none of the functions it calls allow exceptions. So in this case, throwing out of the destructor will trigger std::terminate

independently.



From the 12.4

Destructors section :

A destructor declaration that does not have an exception specification is implicitly considered the same exception specification as an implicit declaration (15.4).

and 15.4

says:

An implicitly declared special member function (clause 12) must have an exception specification. If f is an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or redirect operator, its implicit exception-specification specifies the identifier type T if and only if T is permitted by the exception specification of the function called directly by implicit definition fs; f resolves all exceptions, if any, the function it calls directly, resolves all exceptions, and f allows no exceptions if every function it calls directly does not allow an exception.

In theory, you could use std :: uncaught_exception to detect stack unwrapping in a destructor, but in GotW # 47, Herb Sutter explains why this method is not as useful as it sounds.

Although Herb recently suggested a fix in N4152: unsaught _exceptions

+4


source


In some cases it is possible to add an exception specification like this and handle them with safety. Note that the object is only deleted if there is no exception.

#include<iostream>
using namespace std;

struct A{
    bool a;

    A():a(1){}
    ~A()throw(int){
        if(a)a=0,throw 0;
    }
};

int main(){
    A*a=new A();
    try{
        delete a;
    }catch(int&){
        delete a;
        cout<<"here"<<endl;
    }
    return 0;
}

      

0


source


Others answered from the standard, but I think another example illustrates the conceptual problem better:

struct foo {
    char *buf;

    foo() : buf(new char[100]) {}

    ~foo() {
        if(buf[0] == 'a')
            throw 1;
        delete[] buf;
    }
};

int main() {
    foo *f = new f;
    try {
        delete f;
    } catch(int a) {
        // Now what?
    }
}

      

This simplifies things a bit, of course, but shows the problem. What's the right thing for delete f

? Whichever way it happens, you get either a partially destroyed object or a memory leak.

0


source







All Articles