Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why aren't destructors called when an uncaught exception is throw in C++ during array creation?

class XX
{
public:
    static unsigned s_cnt;
    XX()
    {
        ++ s_cnt;
        std::cout << "C XX " << s_cnt << "\n";

        if ( s_cnt > 2 )
            throw std::exception();
    }

    //private:
    ~XX()
    {
        std::cout << "~ XX\n";
    }
};
unsigned XX::s_cnt = 0;

int main()
{
    try
    {
        XX *xx = new XX[10];
    } catch ( ... )
    {
        std::cout << "Exc\n";
    }
}

Output:

C XX 1
C XX 2
C XX 3
~ XX
~ XX
Exc

But when i remove try-catch, i see:

C XX 1
C XX 2
C XX 3
terminate called after throwing an instance of 'std::exception'
  what():  std::exception
zsh: abort      ./a.out

Why does C++ call the destructors in first case but not in second?

like image 597
pavelkolodin Avatar asked Aug 28 '13 15:08

pavelkolodin


2 Answers

When you don't catch an exception (i.e. it becomes an uncaught exception and terminates your program), C++ doesn't give you any guarantees that destructors actually get called.

This gives the compilers leeway in how to implement exception handling. For example, GCC first searches for a handler. If it can't find one, it aborts immediately, preserving the full stack information for debugging. If it does find one, it actually unwinds the stack, destroying objects, until it arrives at the handler. That's why you don't see output: the program aborts before destroying any objects.

like image 163
Sebastian Redl Avatar answered Sep 19 '22 02:09

Sebastian Redl


When you throw an exception from a constructor, the standard treat the object as not constructed. You don't destroy something that does not exists, don't you?

In fact, even this simple example doesn't work as you suggest:

struct A {
    A() {throw std::exception();}
    ~A() {std::cout << "A::~A()" << std::endl;}
};

int main() {
    A a;
}

This terminates with an uncaught exception, but does not print "A::~A()".

If you think about it, it's the only possible way to give a semantic to it.

For example, we can use our type A as a member object of a class B:

struct B {
    B() : m_a(),m_p(new int) {}
    ~B() {delete m_p;}

    A m_a;
    int * m_p;
};

Obviously B::B() throws immediately (and doesn't even initialize m_p). If an hypothetical C++ standard mandates to calls B::~B() in this case, the destructor has no possible way to know if m_p has been initialized or not.

In other words, throwing an exception from a constructor means that the object never existed and its lifetime never began. This GotW is pretty clear about it.

Bonus: what happens if in the definition of B we swap the order of m_a and m_p ? Then you have a memory leak. That's why exists a particular syntax to catch exception during the initialization of member objects.

like image 20
sbabbi Avatar answered Sep 22 '22 02:09

sbabbi