Why Throwing Exceptions in C++ Destructors Is Dangerous
In C++, throwing an exception from a class destructor is widely
considered an anti-pattern that can lead to catastrophic application
failures. When a destructor emits an unhandled exception, it risks
triggering an immediate and abnormal program termination via
std::terminate, bypassing critical cleanup logic and
leaving the system in an indeterminate state. This article explains the
technical reasons why throwing exceptions during destruction is
hazardous, how it interacts with stack unwinding, and how to safely
handle failures during object cleanup.
The Double-Exception Problem During Stack Unwinding
The primary hazard of throwing an exception from a destructor arises
during stack unwinding. When an exception is thrown in C++, the runtime
begins unwinding the call stack to find a matching catch
block. During this unwinding process, the destructors of all local
objects created within the exited scopes are invoked automatically to
release resources.
If one of these destructors throws a new exception while stack
unwinding is already underway, the C++ runtime finds itself with two
active, concurrent exceptions. C++ does not support handling two
simultaneous active exceptions. Faced with this ambiguity, the runtime
immediately calls std::terminate(). This results in the
abrupt abortion of the entire process without unwinding the rest of the
stack, preventing other destructors from executing and leaving files
unclosed, database connections hanging, and system state corrupted.
Destructors Are Implicitly
noexcept
Beginning with C++11, destructors are implicitly declared as
noexcept(true) unless explicitly marked otherwise or unless
the class contains a member with a throwing destructor.
Because of this implicit declaration, if an exception escapes a
destructor—even when no prior exception was in flight—the program will
not search for an enclosing try-catch block. Instead,
violating the noexcept specification immediately results in
a call to std::terminate(). While a developer can
technically declare a destructor as noexcept(false), doing
so simply re-enables the double-exception risk during stack unwinding
and is strongly discouraged by the C++ Core Guidelines.
Incomplete Object Destruction and Resource Leaks
A destructor is responsible for finalizing an object and releasing all of its held resources, such as heap memory, mutex locks, and file descriptors. If an exception is thrown partway through a destructor's execution:
- The execution of that destructor stops immediately at the throw site.
- Any subsequent cleanup code within that destructor is skipped.
- Resources intended to be released after the throw point are leaked.
Even if the caller manages to catch the exception, the object whose destructor threw it is left in a partially destroyed state, making recovery unreliable and complex.
Safe Alternatives for Error-Prone Cleanup
Destructors should always succeed. When designing classes that manage operations that might fail (such as flushing data to a disk or closing a network socket), follow these best practices:
- Swallow Exceptions Internally: If an operation
inside a destructor might throw, wrap it in a
try-catch(...)block within the destructor. Log the error or handle it silently rather than allowing it to escape. - Provide an Explicit Cleanup Method: Implement a
distinct member function (such as
close(),flush(), orcommit()) that the caller can invoke manually during normal execution. This function can throw exceptions, allowing callers to catch and handle errors appropriately. The destructor then serves solely as a fallback that performs silent cleanup if the caller did not execute the explicit method.