Перейти к содержимому
Теория · Исключения · урок 7 из 9

noexcept и деструкторы

Третья гарантия, отсутствие исключений, записывается прямо в объявлении функции. Это как табличка на этаже: «отсюда мячи тревоги не вылетают». Нижние этажи верят табличке и не натягивают сетку для этого этажа.

Что будет, если мяч всё-таки вылетит? Обещание нарушено, и поймать мяч ниже уже не дадут: здание закрывают целиком.

Обещание noexcept

Слово noexcept после списка параметров обещает, что из функции не выйдет ни одно исключение:

C++
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.

Частая ошибка

Деструктор сообщает об ошибке исключением:

C++
struct File {
    bool saved = false;
    ~File() {
        if (!saved) {
            throw std::runtime_error("not saved");  // ✗
        }
    }
};
TEXT
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 и подпишись на канал — откроются все уроки.

Войти другим способом