Назад к вопросам
Middle+
78
questionbank

Расскажи о вызовах операторов throw для исключений в конструкторах и деструкторах.

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

В конструкторе выброс исключения приводит к тому, что объект не будет полностью сконструирован. Порядок выполнения деструкторов для полностью или частично сконструированных подобъектов определяется стандартом: деструкторы будут вызваны для всех членов и базовых классов, которые были успешно инициализированы до момента генерации исключения.

Проблема выброса исключений из деструктора заключается в следующем: если деструктор вызывается в процессе раскрутки стека из-за другого исключения, и сам выбрасывает исключение, происходит вызов std::terminate(). Это приводит к аварийному завершению программы, так как стандарт не позволяет выбрасывать новое исключение во время обработки существующего. Следовательно, выбрасывать исключения из деструкторов в общем случае крайне не рекомендуется.

Пример выброса исключения в конструкторе:

#include <iostream>
#include <stdexcept>

class Resource {
public:
    Resource() { std::cout << "Resource allocated\n"; }
    ~Resource() { std::cout << "Resource deallocated\n"; }
};

class MyObject {
    Resource res;
public:
    MyObject(bool fail) {
        std::cout << "MyObject constructing...\n";
        if (fail) {
            throw std::runtime_error("Construction failed");
        }
        std::cout << "MyObject constructed\n";
    }
    ~MyObject() {
        std::cout << "MyObject destructing...\n";
    }
};

int main() {
    try {
        MyObject obj1(false); // Успешное конструирование
        // Объект obj1 будет полностью сконструирован, деструктор будет вызван при выходе из scope
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }

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

    try {
        MyObject obj2(true); // Выброс исключения в конструкторе
        // Объект obj2 не будет полностью сконструирован, деструктор Resource будет вызван, MyObject::~MyObject не будет вызван
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }

    return 0;
}

Пример, который может привести к вызову std::terminate() (иллюстративный, в реальном коде так делать не стоит):

#include <iostream>
#include <stdexcept>

class BadDestructor {
public:
    ~BadDestructor() noexcept(false) { // Показываем, что деструктор может бросать исключение
        std::cout << "BadDestructor destructing and throwing\n";
        throw std::runtime_error("Exception from destructor");
    }
};

int main() {
    try {
        // Предположим, здесь происходит нечто, что выбрасывает исключение
        if (true) {
          BadDestructor obj;
          throw std::runtime_error("First exception");
        }
    } catch (const std::exception& e) {
        std::cerr << "Caught first exception: " << e.what() << std::endl;
        // Во время обработки этого исключения вызывается деструктор obj, который бросает новое исключение
    }
    // std::terminate() будет вызван из-за необработанного исключения из деструктора
    return 0;
}

В последнем примере, если деструктор BadDestructor выбрасывает исключение во время обработки другого исключения, программа завершится вызовом std::terminate(). Использование noexcept(false) в сигнатуре деструктора сигнализирует о возможности выброса исключения, но не решает проблему двойного исключения. Стандартная практика - делать деструкторы noexcept(true) (что является поведением по умолчанию в новых стандартах C++).