close
The Wayback Machine - https://web.archive.org/web/20240824170244/https://ru.cppreference.com/w/cpp/language/noexcept_spec
Пространства имён
Варианты
Действия

Спецификатор noexcept (начиная с C++11)

Материал из cppreference.com
< cpp‎ | language
 
 
 
 

Указывает, может ли функция вызывать исключения.

Содержание

[править] Синтаксис

noexcept (1)
noexcept(выражение) (2)
throw() (3) (устарело в C++17)
(удалено в C++20)
1) То же, что и noexcept(true)
2) Если выражение оценивается как true, объявляется, что функция не генерирует никаких исключений. ( следующая за noexcept, всегда является частью этой формы (она никогда не начинает инициализатор).
3) То же, что и noexcept(true) (смотрите семантику спецификации динамических исключений до C++17)
выражение контекстно преобразованное константное выражение типа bool

[править] Объяснение

Спецификация noexcept не является частью типа функции (точно так же, как спецификация динамических исключений) и может появляться только как часть лямбда-декларатор или декларатора-функции верхнего уровня при объявлении функций, переменных, нестатических элементов данных типа функции, указателя на функцию, ссылки на функцию или указателя на функцию-элемент, а также при объявлении параметра или типа возвращаемого значения в одном из этих объявлений, которое, в свою очередь, оказывается указателем или ссылкой на функцию. Она не может появляться в объявлениях typedef или type alias.

void f() noexcept; // функция f() не бросает исключение
void (*fp)() noexcept(false); // fp указывает на функцию, которая может вызывать исключение
void g(void pfa() noexcept);  // g принимает указатель на функцию, которая не вызывает
                              // исключение
// typedef int (*pf)() noexcept; // ошибка
(до C++17)

Спецификация noexcept является частью типа функции и может появляться как часть любого декларатора функции.

(начиная с C++17)

Каждая функция в C++ является либо не генерирующей исключение, либо потенциально генерирующей исключение:

  • потенциально генерирующие исключения функции:
(до C++17)
  • функции, объявленные со спецификатором noexcept, чьё выражение оценивается как false
  • функции, объявленные без спецификатора noexcept, за исключением
  • конструктор для базового класса или элемента, который вызовет неявное определение конструктора, является потенциально генерирующим исключение (смотрите ниже)
  • подвыражение такой инициализации, как выражение аргумента по умолчанию, является потенциально генерирующим исключение (смотрите ниже)
  • инициализатор элемента по умолчанию (только для конструктора по умолчанию) является потенциально генерирующим исключение (смотрите ниже)
  • операторы присваивания копированием, операторы присваивания перемещением, которые неявно объявлены или установлены по умолчанию в их первом объявлении, если только вызов любого оператора присваивания в неявном определении не является потенциально генерирующим исключение (смотрите ниже)
  • операторы сравнения, которые устанавливаются по умолчанию при их первом объявлении, если только вызов любого оператора сравнения в неявном определении не является потенциально генерирующим исключение (смотрите ниже)
(начиная с C++20)
  • негенерирующие исключение функции это все остальные (те, у которых есть спецификатор noexcept, чьё выражение оценивается как true, а также деструкторы, специальные функции-элементы по умолчанию и функции освобождения памяти)

Явная реализация может использовать спецификатор noexcept, но это не обязательно. Если используется, спецификация исключения должна быть такой же, как и для всех других объявлений. Диагностика требуется только в том случае, если спецификации исключений не совпадают в одной единице трансляции.

Функции, отличающиеся только своей спецификацией исключения, не могут быть перегружены (как и возвращаемый тип, спецификация исключения является частью типа функции, но не частью сигнатуры функции) (начиная с C++17).

void f() noexcept;
void f(); // ошибка: другая спецификация исключения
void g() noexcept(false);
void g(); // хорошо, оба объявления для g потенциально выбрасывают исключение

Указатели (включая указатели на функции-элементы) на негенерирующие исключение функции могут быть присвоены или использованы для инициализации (до C++17)неявно преобразуются в (начиная с C++17) указатели на функции, потенциально генерирующие исключение, но не наоборот.

void ft(); // потенциально бросающая исключение
void (*fn)() noexcept = ft; // ошибка

Если виртуальная функция не является генерирующей исключение, все объявления, включая определение, каждого переопределения также не должны вызывать генерацию исключений, если только переопределение не определено как удалённое:

struct B
{
    virtual void f() noexcept;
    virtual void g();
    virtual void h() noexcept = delete;
};
 
struct D: B
{
    void f();          // неправильно: D::f потенциально генерирует исключение,
                       // B::f не генерирует исключение
    void g() noexcept; // OK
    void h() = delete; // OK
};

Функциям без генерирования исключений разрешено вызывать потенциально генерирующие исключения функции. Всякий раз, когда генерируется исключение и поиск обработчика обнаруживает самый внешний блок функции, не вызывающий исключение, вызывается функция std::terminate:

extern void f(); // потенциально бросает исключение
 
void g() noexcept
{
    f();      // допустимо, даже если f бросает исключение
    throw 42; // допустимо, фактически вызов std::terminate
}

Спецификация исключения специализации шаблона функции не создаётся вместе с объявлением функции; оно создаётся только тогда, когда это необходимо (как определено ниже).

Спецификация исключения неявно объявленной специальной функции-элемента также оценивается только при необходимости (в частности, неявное объявление функции-элемента производного класса не требует создания экземпляра спецификации исключения базовой функции-элемента).

Когда спецификация noexcept для специализации шаблона функции необходима, но ещё не была создана, просматриваются зависимые имена, и любые шаблоны, используемые в выражении, создаются, как если бы для объявления специализации.

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

  • в выражении, где функция выбирается разрешением перегрузки
  • функция используется odr
  • функция будет использоваться odr, но появляется в неоценённом операнде
template<class T>
T f() noexcept(sizeof(T) < 4);
 
int main()
{
    decltype(f<void>()) *p; // f не оценивается, но спецификация noexcept не требуется
                            // ошибка, потому что при создании экземпляра спецификации
                            // вычисляется sizeof(void)
}
  • спецификация необходима для сравнения с другим объявлением функции (например, с переопределением виртуальной функции или с явной специализацией шаблона функции)
  • в определении функции
  • спецификация необходима, потому что специальная функция-элемент по умолчанию должна проверить её, чтобы определить свою собственную спецификацию исключения (это происходит только тогда, когда спецификация специальной функции-элемента по умолчанию необходима сама по себе).
(начиная с C++14)


Формальное определение выражения потенциально генерирующего исключение (используется для определения спецификации исключений по умолчанию для деструкторов, конструкторов и операторов присваивания, как описано выше):

Выражение e потенциально генерирует исключение, если:

  • e это вызов функции, указателя на функцию или указателя на функцию-элемент, которая является потенциально генерирующей исключение, если только e это не основное константное выражение (до C++17)
  • e делает неявный вызов потенциально генерирующей исключение функции (такой как перегруженный оператор, функция распределения памяти в выражении new, конструктор для аргумента функции или деструктор, если e полное выражение)
  • e является выражением throw
  • e это dynamic_cast, который приводит полиморфный ссылочный тип
  • e это выражение typeid, применяемое к разыменованному указателю на полиморфный тип
  • e имеет непосредственное подвыражение, которое может вызвать исключение
struct A
{
    A(int = (A(5), 0)) noexcept;
    A(const A&) noexcept;
    A(A&&) noexcept;
    ~A();
};
 
struct B
{
    B() throw();
    B(const B&) = default; // неявная спецификация исключения noexcept(true)
    B(B&&, int = (throw Y(), 0)) noexcept;
    ~B() noexcept(false);
};
 
int n = 7;
struct D : public A, public B
{
    int * p = new int[n];
    // D::D() потенциально генерирует исключение из-за оператора new
    // D::D(const D&) не генерирует исключение
    // D::D(D&&) потенциально генерирует исключение: аргумент по умолчанию
    // для конструктора B может генерировать исключение
    // D::~D() потенциально генерирует исключение
 
    // примечание; если A::~A() была бы виртуальной, то программа была бы некорректной,
    // потому что переопределение негенерирующего исключение виртуального деструктора
    // не может быть потенциально генерирующим исключение
};

[править] Примечание

Одно из применений константного выражения (вместе с оператором noexcept) для определения шаблонов функций, которые объявляют noexcept для одних типов, но не для других.

Обратите внимание, что спецификация функции noexcept не является проверкой времени компиляции; это просто метод для программиста сообщить компилятору, должна ли функция генерировать исключения. Компилятор может использовать эту информацию для включения определённых оптимизаций функций, не вызывающих генерацию, а также для включения оператора noexcept, который может проверять во время компиляции, объявлено ли конкретное выражение для выбрасывания любого исключения. Например, такие контейнеры, как std::vector, будут перемещать свои элементы, если конструктор перемещения элементов имеет значение noexcept, и копировать в противном случае (если только конструктор копирования недоступен, но существует потенциально бросающий исключение конструктор перемещения, в этом случае гарантия сильного исключения отменяется).

[править] Устарело

noexcept это улучшенная версия throw(), которое устарело в C++11. В отличие от пред-C++17 throw(), noexcept не будет вызывать std::unexpected, может или не может раскручивать стек и вызывать std::terminate, что потенциально позволяет компилятору реализовать noexcept без накладных расходов во время выполнения throw(). Начиная с C++17, throw() переопределяется, чтобы быть точным эквивалентом noexcept(true).

Макрос Тестирования функциональности Значение Стандарт Функциональность
__cpp_noexcept_function_type 201510L (C++17) Спецификации исключений сделаны частью системы типов

[править] Ключевые слова

noexcept

[править] Пример

// объявлена ли foo noexcept зависит вызовет ли выражение
// T() какие-либо исключения
template<class T>
void foo() noexcept(noexcept(T())) {}
 
void bar() noexcept(true) {}
void baz() noexcept { throw 42; } // noexcept это то же самое, что и noexcept(true)
 
int main() 
{
    foo<int>(); // noexcept(noexcept(int())) => noexcept(true), так что это нормально
 
    bar(); // отлично
    baz(); // компилируется, но во время выполнения вызывает std::terminate
}

[править] Отчёты о дефектах

Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:

Номер Применён Поведение в стандарте Корректное поведение
CWG 1740 C++11 ( после noexcept может запустить инициализатор это может быть только часть спецификации noexcept
CWG 2039 C++11 только выражение перед преобразованием должно быть
константным
преобразование также должно быть действительным
в константном выражении

[править] Смотрите также

оператор noexcept(C++11) определяет, вызывает ли выражение какие-либо исключения[править]
Спецификация динамического исключения(до C++17) указывает, какие исключения вызываются функцией (устарело в C++11) [править]
выражение throw сигнализирует об ошибке и передаёт управление обработчику ошибок[править]
получает ссылку rvalue, если конструктор перемещения не генерирует исключение
(шаблон функции) [править]