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:

  1. The runtime automatically invokes the matching operator delete to reclaim the raw memory allocated for the object.
  2. The exception propagates out of the new expression 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 catch block without explicitly throwing an exception, the runtime implicitly rethrows the original exception.
  • Dead Members: Inside the catch block, 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.