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?
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.
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.
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