Ответ
Double exception (двойное исключение) — это ситуация, когда второе исключение выбрасывается во время обработки (раскрутки стека) первого исключения. До стандарта C++11 это приводило к немедленному вызову std::terminate() и аварийному завершению программы.
Проблемный пример (до C++11):
#include <iostream>
#include <stdexcept>
void cleanup() {
throw std::logic_error("Ошибка в cleanup!"); // Второе исключение во время раскрутки стека!
}
void foo() {
throw std::runtime_error("Первичная ошибка в foo");
}
int main() {
try {
foo();
} catch (const std::runtime_error& e) {
std::cout << "Поймано: " << e.what() << 'n';
cleanup(); // Выбрасывает второе исключение -> std::terminate() до C++11
}
return 0;
}
Как обрабатывается сейчас (C++11 и новее):
Стандарт C++11 ввёл понятие исключения в процессе обработки исключения (exception during stack unwinding). Если такое происходит, вызывается std::terminate(). Однако, если второе исключение генерируется непосредственно внутри catch-блока, оно обрабатывается обычным образом, так как раскрутка стека для первого исключения уже завершена.
Правильные практики для предотвращения проблем:
- Делать деструкторы и функции очистки
noexcept:~MyResourceHolder() noexcept { /* гарантированно не бросаем исключения */ } - Использовать
std::optionalили флаги состояния в функциях, которые могут вызываться из catch-блоков. - Логировать ошибки вместо выбрасывания исключений в критичных местах (деструкторы, обработчики исключений).
Пример безопасного подхода с std::nested_exception (C++11):
#include <iostream>
#include <stdexcept>
#include <exception>
void safe_cleanup() noexcept { /* ... */ }
void foo() {
try {
throw std::runtime_error("Ошибка уровня 1");
} catch (...) {
// Сохраняем текущее исключение и бросаем новое с контекстом
std::throw_with_nested(std::logic_error("Добавлен контекст"));
}
}
int main() {
try {
foo();
} catch (const std::exception& e) {
std::cerr << "Ошибка: " << e.what() << 'n';
try {
std::rethrow_if_nested(e); // Раскручиваем вложенное исключение, если есть
} catch (const std::exception& nested) {
std::cerr << "tПричина: " << nested.what() << 'n';
}
safe_cleanup(); // Безопасная очистка
}
}
Главное правило: исключения в деструкторах и во время обработки других исключений — это опасные ситуации, которых нужно избегать.