What Happens When a C++ Constructor Throws?
When an exception is thrown from an object constructor in C++, the object is considered to have never officially existed, meaning its destructor will not be executed. However, C++ guarantees that any base classes and member objects that were fully constructed before the exception occurred are destroyed in the reverse order of their initialization. Understanding this mechanism is critical for proper resource management and avoiding memory leaks in modern C++ programming.
The Object Never Officially Exists
In C++, an object's lifetime formally begins only after its constructor has successfully completed execution. If an exception is thrown inside the constructor body—or during the initialization of its member fields or base classes—the construction process is aborted.
Because the object's lifetime never started, the compiler will never call the destructor of the class whose constructor failed.
Automatic Destruction of Initialized Sub-objects
Although the main object's destructor does not run, the C++ runtime handles sub-objects that have already been initialized:
- Base Classes: Any base class sub-objects that completed construction will have their destructors called in the reverse order of their inheritance declaration.
- Member Variables: Any non-static member variables whose construction succeeded before the throw will have their destructors called in the reverse order of their declaration in the class definition.
- Failing Member: The member whose constructor threw the exception is cleaned up by its own internal exception-handling mechanisms; its destructor is not called.
- Uninitialized Members: Any members declared after the point of failure are left untouched and their destructors are not invoked.
Raw Pointers and Resource Leaks
A major hazard arises when managing resources manually using raw pointers inside a constructor:
class ResourceHolder {
int* data1;
int* data2;
public:
ResourceHolder() {
data1 = new int[100];
// If an exception occurs here, data1 will leak
throw std::runtime_error("Failure");
data2 = new int[200];
}
~ResourceHolder() {
delete[] data1;
delete[] data2;
}
};In the example above, because ~ResourceHolder() never
runs, delete[] data1 is never executed, causing a memory
leak.
To prevent this, C++ relies on the RAII (Resource
Acquisition Is Initialization) idiom. If data1 and
data2 are wrapped in smart pointers such as
std::unique_ptr<int[]>, the runtime automatically
calls their destructors during stack unwinding, safely deallocating the
memory.
Dynamic Allocation with
operator new
If an object is being allocated on the heap using the
new operator and its constructor throws an exception:
- The runtime automatically invokes the matching
operator deleteto reclaim the raw memory allocated for the object. - The exception propagates out of the
newexpression to the caller.
This ensures that the memory allocated by the new
expression is not leaked, even though the object was never fully
created.
Function-Try-Blocks in Constructors
Constructors can use function-level try-catch blocks to
catch exceptions thrown during member initializer list execution or
within the constructor body:
class Example {
Member m;
public:
Example() try : m() {
// Constructor body
} catch (...) {
// Handle or log the error
}
};Key rules regarding constructor function-try-blocks include:
- Mandatory Rethrow: You cannot absorb or suppress an
exception in a constructor's function-try-block. If control reaches the
end of the
catchblock without explicitly throwing an exception, the runtime implicitly rethrows the original exception. - Dead Members: Inside the
catchblock, all non-static members and base classes have already been destroyed. Attempting to access them results in undefined behavior. - Primary Use Case: These blocks are primarily used to log failures, transform exception types, or clean up non-object resources (such as raw OS handles) acquired prior to the throw.