Sobes.tech
Middle+

What is the peculiarity of throwing exceptions in constructors and destructors?

sobes.tech AI

Answer from AI

Exception throwing in constructor:

  • If an exception is thrown in a constructor, the destructor of the object is not called.
  • It is necessary to take care of releasing already allocated resources before the exception is thrown or use the RAII (Resource Acquisition Is Initialization) idiom for resource management.
  • A partially constructed object is in an undefined state.

Exception throwing in destructor:

  • Throwing an exception from a destructor during stack unwinding due to another exception leads to a call to std::terminate, which terminates the program. This is very bad.
  • Destructors should be noexcept.
  • If an operation that may throw an exception (e.g., closing a file) needs to be performed in a destructor, it should be handled inside the destructor (e.g., logging) instead of propagating the exception further.
#include <iostream>
#include <fstream>

class ResourceHolder {
    std::ofstream file;
    bool opened = false;

public:
    ResourceHolder(const std::string& filename) {
        file.open(filename);
        if (!file.is_open()) {
            // Throw exception in constructor
            throw std::runtime_error("Failed to open file: " + filename);
        }
        opened = true;
        std::cout << "Resource acquired successfully." << std::endl;
    }

    ~ResourceHolder() noexcept { // Destructor marked noexcept
        if (opened) {
            try {
                file.close();
                std::cout << "Resource released successfully." << std::endl;
            } catch (const std::exception& e) {
                // Handle exception inside destructor
                std::cerr << "Exception caught in destructor: " << e.what() << std::endl;
                // Do not throw further
            }
        } else {
             std::cout << "Resource release skipped (not opened)." << std::endl;
        }
    }
};

int main() {
    try {
        // Example of exception in constructor
        // ResourceHolder res("non_existent_file.txt");

        // Example of successful construction and release
        ResourceHolder res("test.txt");

    } catch (const std::runtime_error& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }

    // Example of incorrect destructor (for illustration, DO NOT REPEAT)
    /*
    class BadDestructor {
        int* data;
    public:
        BadDestructor() : data(new int(10)) {}
        ~BadDestructor() {
            delete data;
            // Simulate exception in destructor (DO NOT DO THIS)
            // throw std::runtime_error("Exception from bad destructor");
        }
    };

    try {
        BadDestructor bad_obj;
        throw std::runtime_error("Another exception"); // Throw another exception
    } catch (...) {
        // When an exception occurs in destructor during stack unwinding
        // std::terminate will be called
        std::cerr << "Caught some exception in main." << std::endl;
    }
    */

    return 0;
}
Feature Constructor Destructor
Destructor call Not called if an exception is thrown Usually called (if construction is successful)
Undefined state Partially constructed object -
RAII Important for resource management -
noexcept Not mandatory but useful to know about error possibility Highly recommended to be noexcept
Stack unwinding Participates in the process Throwing an exception during unwinding leads to std::terminate