В каких случаях деструкторы объектов C++ могут не вызваться при возникновении исключения?

«В каких случаях деструкторы объектов C++ могут не вызваться при возникновении исключения?» — вопрос из категории Управление памятью, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Деструкторы автоматических (стековых) объектов гарантированно вызываются при выходе из их области видимости в процессе раскрутки стека (stack unwinding). Однако есть сценарии, где эта гарантия нарушается:

  1. Исключение, покинувшее функцию main() без перехвата. Программа завершается вызовом std::terminate(), и раскрутка стека не гарантируется.

    int main() {
        ResourceHandle res; // Деструктор может не вызваться
        throw std::runtime_error("Uncaught!");
        // Завершение через std::terminate()
    }
  2. Исключение, выброшенное в деструкторе во время активной обработки другого исключения. Это приводит к немедленному вызову std::terminate() (правило: деструкторы не должны выбрасывать исключения).

    struct Bad {
        ~Bad() noexcept(false) { throw 42; } // Опасность!
    };
    int main() {
        try {
            Bad obj;
            throw std::runtime_error("First"); // Начало раскрутки стека
        } catch(...) { } // Сюда не попадем, вызовется terminate()
    }
  3. Некорректное использование низкоуровневых средств управления потоком выполнения, обходящих механизм исключений C++.

    • Функции longjmp()/setjmp() из языка C. Переход longjmp() через кадры стека с объектами C++ ведет к неопределенному поведению.
    • Прямой вызов std::exit() или std::abort(). Раскрутка стека не производится.
  4. Утечка указателя на динамически выделенную память. Если исключение возникает до вызова delete для сырого указателя, деструктор объекта в куче не вызовется.

    void risky() {
        MyClass* ptr = new MyClass;
        someFunctionThatMayThrow(); // Может выбросить исключение
        delete ptr; // Эта строка может не выполниться
    }

    Решение: Использовать умные указатели (std::unique_ptr, std::shared_ptr), чьи деструкторы вызовут delete даже при исключении (паттерн RAII).