Writing fairly straightforward C++ code, we usually make heavy use of the RAII concept. Therefore, we greatly rely on the simple (and basic) assumption that all appropriate destructors will be called. What happens if that is not the case?

Consider the following piece of code:

#include <iostream> #include <stdexcept> struct A { A () { std::cout << "A" << std::endl; } ~A () { std::cout << "~A" << std::endl; } }; void myfunc () { A temp; throw std::runtime_error("moo"); } int main () { myfunc(); }

You would probably imagine that there is absolutely nothing special about this code, and that both the constructor and the destructor should be called to produce their outputs. However, that is not what actually happens.

The given program produces this output (at least with my XCode):

A

terminate called throwing an exception

The most important detail to take note of in this example, is that the output of A’s destructor is nowhere to be found – the destructor call is missing. What if that destructor was supposed to free a lock or a mutex, or worse – some global system resource? That could be nothing short of a disaster.

Digging further, adding a catch clause to the outermost code would solve the issue:

int main () { try { myfunc(); } catch (...) { // do nothing } }

However, I find the reasoning for this phenomena to be the interesting part of the story. Looking at the C++ standard draft, section 15.3 [except.handle] point 9 reads:

If no matching handler is found in a program, the function terminate()

(_except.terminate_) is called. Whether or not the stack is unwound

before calling terminate() is implementation-defined.

So what happens here is that since the original program never attempts to catch the given exception, unexpected() ends up getting called. Reading the given section of the C++ standard lets us know that stack unwinding is not mandatory in this case, and as such – many compilers are likely to skip implementing it. As far as I know, both gcc and Visual Studio behave the same in this context, and do not perform a full stack unwinding in this case.

Moreover, a bug of this nature actually happened to a colleague of mine: essential destructor code was missing from actual production execution, causing a leak of global system resources. That was a hard bug to track.