Sobes.tech
Middle+

Is it possible to throw an exception from a destructor and catch it?

sobes.tech AI

Answer from AI

Yes, but it is highly not recommended. Throwing an exception from a destructor can lead to undefined behavior, especially if the destructor is called during stack unwinding due to another exception.

#include <iostream>
#include <stdexcept>

struct Resource {
    ~Resource() {
        // Throwing exceptions from destructors is highly discouraged
        std::cerr << "Resource destructor called." << std::endl;
        // throw std::runtime_error("Exception from Resource destructor");
    }
};

void func_with_exception() {
    Resource r;
    throw std::runtime_error("Exception from func_with_exception");
}

int main() {
    try {
        func_with_exception();
    } catch (const std::exception& e) {
        // If an exception is thrown during stack unwinding
        // terminate() or other undefined behavior may occur.
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }
    return 0;
}

Main reasons why throwing exceptions from destructors is discouraged:

  • Double exceptions: If a destructor is called during the handling of another exception (e.g., during stack unwinding), throwing a new exception will lead to two active exceptions simultaneously, which is undefined behavior and often results in std::terminate().
  • Incomplete resource cleanup: If an error occurs in a destructor and an exception is thrown, subsequent cleanup operations in the same destructor may not execute, leading to resource leaks or other issues.
  • Complex error management: Handling exceptions from destructors complicates program logic and makes the flow harder to understand.

In C++11 and later, destructors are considered noexcept by default, meaning they should not throw exceptions. If a noexcept destructor throws, std::terminate() is called immediately. To allow a destructor to throw exceptions, it must be explicitly declared as noexcept(false).

#include <iostream>
#include <stdexcept>

struct ResourceMayThrow {
    ~ResourceMayThrow() noexcept(false) {
        std::cerr << "ResourceMayThrow destructor called." << std::endl;
        // In this case, throwing an exception is possible but still not recommended
        // throw std::runtime_error("Exception from ResourceMayThrow destructor");
    }
};

int main() {
    try {
        ResourceMayThrow r;
        // If an exception occurs here and the destructor also throws,
        // it will lead to std::terminate().
    } catch (const std::exception& e) {
        std::cerr << "Caught exception in main: " << e.what() << std::endl;
    }
    return 0;
}

Instead of throwing exceptions from destructors, it is recommended to use the following approaches for error handling during resource release:

  • Return error codes: If a destructor can fail, the object can store an error flag that can be checked after destruction.
  • Logging: Log errors during resource cleanup to a log file or standard error.
  • Implement a close() or release() function: Provide a separate function for explicit resource release that can throw exceptions, and call it before the object goes out of scope. RAII objects should guarantee resource release in their destructors even if close() was not explicitly called.
Approach Is it recommended? Notes
Throwing exceptions No Can lead to std::terminate() and undefined behavior.
Returning error codes Yes (if necessary) Requires explicit error checking after using the object.
Logging Yes Useful for debugging but does not stop program execution.
close/release function Yes Allows explicit error handling during cleanup, making destructors safe.

Overall, a safe destructor is one that never throws exceptions, which is part of the RAII (Resource Acquisition Is Initialization) principle.