Sobes.tech
Middle+

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

sobes.tech AI

Answer from AI

Throwing exceptions from a constructor results in the object not being fully created. In this case, destructors for all already-initialized sub-objects (data members and base classes) are automatically called.

Throwing exceptions from a destructor is highly undesirable. If an exception is thrown during the handling of another active exception (for example, during stack unwinding), the program will terminate by calling std::terminate. Even in the absence of another active exception, throwing an exception from a destructor can disrupt the expected flow of execution and make the code unpredictable. It is recommended to handle all exceptions inside the destructor or design the code so that the destructor cannot throw an exception.

#include <iostream>
#include <stdexcept>

class Resource {
public:
    Resource() {
        std::cout << "Resource acquired" << std::endl;
    }
    ~Resource() noexcept { // Destructor should not throw exceptions
        try {
            // Potentially dangerous operation that might throw
            // std::cout << "Resource released (potentially throwing op)" << std::endl;
            // throw std::runtime_error("Error during release");
            std::cout << "Resource released" << std::endl;
        } catch (...) {
            // Logging or other error handling without rethrowing
            std::cerr << "Exception caught in Resource destructor!" << std::endl;
            // std::abort(); // Extreme case for unrecoverable errors
        }
    }
};

class MyClass {
private:
    Resource res1;
    Resource res2; // This resource will be created after res1
    Resource res3;

public:
    MyClass(int value) : res1(), res2(), res3() {
        std::cout << "MyClass constructor started" << std::endl;
        if (value < 0) {
            // Exception in constructor
            // res1 and res2 are already created and will be automatically destroyed
            throw std::runtime_error("Negative value not allowed");
        }
        std::cout << "MyClass constructor finished" << std::endl;
    }

    ~MyClass() {
        std::cout << "MyClass destructor called" << std::endl;
        // Destructors for res1, res2, res3 will be called automatically
    }
};

int main() {
    try {
        MyClass obj1(10); // Successful creation
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }

    std::cout << "---" << std::endl;

    try {
        MyClass obj2(-5); // Exception in constructor
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }

    std::cout << "---" << std::endl;

    // Example with a potential exception in destructor (commented out)
    // MyClass obj3(20);
    // (obj3 will be destroyed at program exit, calling destructor)

    return 0;
}