noexcept и деструкторы
Третья гарантия, отсутствие исключений, записывается прямо в объявлении функции. Это как табличка на этаже: «отсюда мячи тревоги не вылетают». Нижние этажи верят табличке и не натягивают сетку для этого этажа.
Что будет, если мяч всё-таки вылетит? Обещание нарушено, и поймать мяч ниже уже не дадут: здание закрывают целиком.
Обещание noexcept
Слово noexcept после списка параметров обещает, что из функции не выйдет ни одно исключение:
int twice(int x) noexcept {
return x * 2;
}Зачем это нужно? Обещание читают и люди, и стандартная библиотека. Например, std::vector при росте может переносить элементы быстрее, если знает, что перенос не бросит исключение.
Что происходит, если обещание нарушено:
- компилятор не запрещает
throwвnoexcept-функции: GCC и Clang только предупреждают, если видят, что исключение из неё выйдет; - если исключение всё же покидает
noexcept-функцию, вызываетсяstd::terminate, и программа завершается; - обработчик снаружи такое исключение не поймает, даже если его тип подходит;
- исключение, которое брошено и поймано внутри самой функции, обещания не нарушает.
Деструкторы и noexcept
Любой деструктор считается noexcept, даже если это слово не написано. Поэтому исключение, покинувшее деструктор, тоже вызывает std::terminate.
Вспомните раскрутку стека: деструкторы выполняются как раз тогда, когда одно исключение уже летит. Если бы деструктор бросил второе, программе пришлось бы выбирать между двумя мячами. C++ не выбирает и завершает программу.
Этот вопрос любят на собеседовании: «почему деструктор не должен бросать исключения?» Ответ — деструктор неявно noexcept, и исключение из него вызывает std::terminate.
Частая ошибка
Деструктор сообщает об ошибке исключением:
struct File {
bool saved = false;
~File() {
if (!saved) {
throw std::runtime_error("not saved"); // ✗
}
}
};warning: '~File' has a non-throwing exception specification but can still throw [-Wexceptions]Программа собирается: это предупреждение Clang, и GCC выдаёт похожее. Но когда уничтожается объект File с saved, равным false, вызывается std::terminate, и try вокруг объекта не поможет.
Деструктор не должен бросать. Об ошибке сообщает отдельный метод вроде save(), который вызывают до уничтожения объекта. Там исключение ещё можно поймать.
Главное
noexceptобещает, что из функции не выйдет ни одно исключение; компиляторthrowв ней не запрещает.- Исключение, покинувшее
noexcept-функцию, вызываетstd::terminate, и внешний обработчик его не поймает. - Любой деструктор считается
noexcept, даже без этого слова. - Деструктор не бросает исключений: об ошибке сообщает отдельный метод, который вызывают раньше.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.