noexcept 指定符 (C++11 起)

来自cppreference.com
< cpp‎ | language

指定函数是否抛出异常。

目录

[编辑] 语法

noexcept (1)
noexcept(expression) (2)
throw() (3) (弃用)
1)noexcept ( true )
2)expression 求值为 true ,则函数声明为不抛出任何异常。
3)noexcept(true)
(C++17 起)
3) 不抛出动态异常规定(不同于 noexcept(true) ,它保证栈回溯并可能调用 std::unexpected
(C++17 前)
expression - 根据语境转换到 bool 类型的常量表达式

[编辑] 解释

noexcept 规定是函数类型的一部分,并可以作为函数声明器的一部分出现。

(C++17 起)

noexcept 规定不是函数类型的一部分(正如同动态异常规定),而且只能在声明函数、变量、函数类型的非静态数据成员、指向函数指针、到函数的引用或指向成员函数的指针时,和声明类型正好是指向函数指针或到函数的引用的参数或返回类型时,作为 lambda 声明器或顶层函数声明器的一部分出现。它不能出现于 typedef类型别名声明。

void f() noexcept; // 函数 f() 不抛出
void (*fp)() noexcept(false); // fp 指向可能抛出的函数
void g(void pfa() noexcept);  // g 接收指向不抛出的函数的指针
// typedef int (*pf)() noexcept; // 错误
(C++17 前)

C++ 中每个函数为不抛出潜在抛出之一。

  • 潜在抛出的函数是:
(C++17 前)
  • expression 求值为 false 的 noexcept 指定符声明的函数
  • 不以 noexcept 声明的函数,除了
  • 构造函数的隐式定义会调用的基类或成员的一个构造函数为潜在抛出(见后述)
  • 这种初始化的一个子表达式,例如默认参数表达式,为潜在抛出(见后述)
  • 默认成员初始化器(仅对默认构造函数)为潜在抛出(见后述)
  • 不抛出函数为所有其他函数(以 expression 求值为 true 的 noexcept 指定符声明的函数,还有构造函数、默认特殊成员函数及解分配函数)

显式实例化可使用 noexcept 指定符,但不要求。若使用,则异常规定必须同所有其他声明。仅若异常规定不在同一翻译单元相同,才要求诊断。

仅于异常规定有别的函数不能重载(正如返回类型,异常规定是函数类型的一部分,但不是函数签名的一部分) (C++17 起)

void f() noexcept;
void f(); // 错误:不同的异常规定
void g() noexcept(false);
void g(); // ok , 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 规定时,查找依赖名并实例化任何用于 expression 的模板,如同对于特化的声明。

函数的 noexcept 规定在下列语境被认为需要

  • 在表达式中,其中函数为重载决议所选择
  • 函数被 odr 使用
  • 函数本该被 odr 使用但出现于不求值运算数中
template<class T> T f() noexcept(sizeof(T) < 4);
int main() {
    decltype(f<void>()) *p; // f 不求值,但需要 noexcept 规定
}
  • 特化需要与另一函数声明比较(例如在虚函数覆写或函数模板的显式特化处)
  • 在函数定义中
  • 需要规定,因为需要检查默认特殊成员函数以决定其自身的异常规定(这只在需要默认化的特殊成员函数自身时发生)。
(C++14 起)


潜在抛出表达式的正式定义是(用于确定上述析构函数、构造函数和赋值运算符的默认异常规定):

动态异常规定

(C++17 前)

表达式 e潜在抛出,若:

  • e 是对潜在抛出的函数或指向函数的指针的调用
  • e潜在抛出函数进行隐式调用(例如作为重载运算符、 new 表达式中的分配函数、函数参数的构造函数或析构函数,若 e 为完整表达式)
  • ethrow 表达式
  • 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() 为虚,则此程序将为病态,因为不抛出 virtual 的覆写不能为潜在抛出
};
(C++17 起)

[编辑] 注意

常量 expression (带有 noexcept operator 运算符)的一种用法是定义函数模板,以对一些类型声明 noexcept ,但不对其他声明。

注意函数上的 noexcept 规定不是编译时检查;它只不过是程序员提示编译器函数是否会抛异常的方法。编译器能用此信息启用不抛出函数上的某些优化,同启用能在编译时检查特定表达式是否声明为可抛出任何异常的 noexcept 运算符。例如,诸如 std::vector 的容器会在元素的移动构造函数为 noexcept 的情况移动元素,否则复制元素(除非复制构造函数不可访问,但潜在抛出的移动构造函数只在放弃强异常保证的情况考虑)。

[编辑] 废弃

noexceptthrow() 的改进版本,后者于 C++11 中过时。不同于 C++17 前的 throw()noexcept 不会调用 std::unexpected 并且可能或可能不进行栈回溯,这潜在地允许编译器实现不带 throw() 的运行时开销的 noexcept 。自 C++17 起, throw() 被重定义为准确等价于 noexcept(true)

[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 2039 C++11 仅要求转换前的表达式为常量 转换必须也在常量表达式中合法

[编辑] 关键词

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
}


[编辑] 参阅

noexcept 运算符 确定表达式是否会抛出任何异常 (C++11 起) [编辑]
异常规定 指定函数所抛出的异常 (弃用) [编辑]
throw 表达式 引发错误并将控制转移到错误处理函数[编辑]
若移动构造函数不抛出则获得右值引用
(函数模板) [编辑]