В C++ обработка исключений сопровождается механизмом stack unwinding — автоматического разрушения (деструкторизации) локальных объектов в стеке вызовов, созданных до возникновения исключения. Это явление важно для корректного освобождения ресурсов.
Изначально, при проектировании C++, недостаёт built-in сборщика мусора, и задача освобождения ресурсов (памяти, файлов, сокетов) ложится на программиста. Обработка исключений должна была обеспечивать корректное высвобождение при откате кода из-за ошибки.
Без механизма автоматического разрушения объектов при исключении возникает утечка ресурсов. Если не освобождать ресурсы вручную в каждом блоке catch, код становится сложным и ненадёжным.
C++ реализует stack unwinding, когда при выбрасывании исключения все объекты, размещённые на стеке в области видимости до throw, уничтожаются в обратном порядке создания. Их деструкторы вызываются автоматически.
Классический способ постоянно освобождать ресурсы — применять паттерн RAII (Resource Acquisition Is Initialization): все ресурсы "завёрнуты" в объекты, которые при разрушении освобождают владеемый ими ресурс.
Пример кода:
#include <iostream> #include <stdexcept> struct FileHandle { FILE* file; FileHandle(const char* path) { file = fopen(path, "r"); if (!file) throw std::runtime_error("Cannot open file"); } ~FileHandle() { if (file) fclose(file); } }; void processFile(const char* path) { FileHandle fh(path); // Откатит, если будет throw // ... работа с файлом throw std::runtime_error("Some error"); // fclose автоматически вызовется }
Ключевые особенности:
Почему нельзя просто воспользоваться try-catch и вручную вызвать delete или fclose в блоке catch, чтобы избежать утечек?
Ответ:
Это неудобно и ненадёжно: легко забыть закрыть ресурс, особенно при нескольких точках выхода или вложенных ресурсах. Деструкторы вызываются даже если исключение "пролетает" через функцию, catch для этого не нужен.
Будут ли уничтожены объекты в куче (heap), если они не "завёрнуты" в объекты на стеке при stack unwinding?
Ответ:
Нет. Stack unwinding вызывает деструкторы только для объектов размещённых на стеке. Чтобы heap-объекты уничтожались корректно, ими должен владеть объект в стеке (например, через smart pointers).
Что будет, если деструктор при stack unwinding сам выбрасывает исключение?
Ответ:
Если в процессе stack unwinding во время разрушения какого-либо объекта будет выброшено второе исключение, программа завершится вызовом std::terminate(). Никогда не выбрасывайте исключения из деструкторов!
Разработчик вручную закрывает файлы и освобождает память через catch-блоки без применения RAII.
Плюсы:
Минусы:
Файлы и ресурсы оборачиваются в классы-обёртки RAII. Итог: освобождение происходит в деструкторах, независимо от throw/catch.
Плюсы:
Минусы: