Sobes.tech
Middle+

When do destructors not get called during an exception?

sobes.tech AI

Answer from AI

Destructors of local objects with automatic storage duration are not called if an exception is thrown in the constructor and the object was not fully constructed.

Also, destructors are not called for:

  • Objects with global or static storage duration.
  • Objects in raw memory that are not managed by standard mechanisms (for example, allocated via malloc and not freed).
  • Objects pointed to by leaking smart pointers.
  • Objects whose destructors are marked as noexcept(false) and throw exceptions during stack unwinding. In this case, the program usually terminates abnormally (std::terminate).

Example of destructor not being called in a constructor:

#include <iostream>
#include <stdexcept>

class Resource {
public:
    Resource() {
        std::cout << "Resource acquired" << std::endl;
    }
    ~Resource() {
        std::cout << "Resource released" << std::endl;
    }
};

class MyClass {
public:
    Resource res1; // This destructor will be called if an exception is thrown in MyClass constructor
    // Resource res2; // If an exception is thrown here, res2 is not constructed

    MyClass() {
        // res1 is already constructed
        std::cout << "MyClass constructor" << std::endl;
        throw std::runtime_error("Error in MyClass constructor");
        // res2 = Resource(); // This object will not be constructed if an exception is thrown above
    }
    ~MyClass() {
        std::cout << "MyClass destructor" << std::endl;
    }
};

int main() {
    try {
        MyClass obj; // Exception occurs here
    } catch (const std::runtime_error& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }
    return 0;
}

In this example, the destructor of MyClass is not called because the constructor of MyClass did not complete successfully. However, the destructor of res1 (a member of MyClass) will be called because res1 was successfully constructed before the exception was thrown.

Example of destructor not being called during a second exception:

#include <iostream>
#include <stdexcept>

class BadDestructor {
public:
    BadDestructor() {
        std::cout << "BadDestructor constructor" << std::endl;
    }
    ~BadDestructor() noexcept(false) {
        std::cout << "BadDestructor destructor" << std::endl;
        throw std::runtime_error("Error in destructor"); // Throws exception during stack unwinding
    }
};

void foo() {
    BadDestructor obj;
    throw std::runtime_error("Exception in foo");
}

int main() {
    try {
        foo();
    } catch (const std::runtime_error& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }
    // std::terminate will be called before this block executes
    return 0;
}

In this case, when the exception is thrown in foo, stack unwinding begins. The destructor of BadDestructor is called. Since this destructor throws an exception (noexcept(false) allows this), a second exception occurs during the handling of the current exception. According to the C++ standard, in such a situation, std::terminate is called, and the program terminates. Destructors of other objects in the stack (if any before foo) may not be called.