Что происходит при двойном исключении в деструкторе и как избежать undefined behavior?
Что проверяют
Знаете ли вы, чем заканчивается исключение, покидающее деструктор, и верное ли у вас представление о последствиях. Правильный ответ тут не про неопределённое поведение, а про немедленное завершение программы.
Как отвечать
Если во время раскрутки стека, то есть при обработке уже летящего исключения, деструктор локального объекта выбрасывает своё, рантайму не из чего выбирать: продолжить можно только одно, и он вызывает std::terminate. Это поведение, описанное стандартом, а не неопределённое, и внешним catch его не перехватить.
Начиная с одиннадцатого стандарта правило ещё строже: деструктор по умолчанию помечен noexcept, поэтому исключение, вышедшее за его границу, приводит к std::terminate даже без всякой раскрутки.
Отсюда практический вывод: деструктор не выпускает исключения наружу.
~FileWriter() noexcept {
try { flushAndClose(); }
catch (...) { logFailure(); } // гасим здесь
}Если ошибка освобождения ресурса важна вызывающему, ей нужен явный метод, который её возвращает или бросает, а деструктор остаётся страховкой на случай, когда метод не позвали. Так устроены файловые потоки стандартной библиотеки: закрытие в деструкторе ошибку не сообщает. Полезно добавить, что типы в контейнерах обязаны иметь небросающий деструктор, иначе контейнер не даёт вообще никаких гарантий.
Где спотыкаются
Называют результат неопределённым поведением. Разница принципиальна: неопределённое поведение может годами выглядеть рабочим, а terminate убивает процесс сразу и одинаково, и причину надо искать в стеке завершения, а не в повреждённой памяти.
Второе: думают, что достаточно снять ограничение через noexcept(false). Так деструктор действительно сможет бросать в спокойной ситуации, но при раскрутке всё равно случится завершение, а объекты вокруг останутся неразрушенными.
Третье: пишут catch (...) и молчат. Проглоченная без следа ошибка сброса буфера на диск означает потерянные данные, о которых никто не узнает. Внутри деструктора надо хотя бы записать в журнал или выставить флаг, который проверят снаружи.