noexcept
说明符 (C++11 起)
指定函数是否可能抛出异常。
目录 |
[编辑] 语法
noexcept
|
(1) | ||||||||
noexcept( 表达式) |
(2) | ||||||||
throw()
|
(3) | (C++17 中已弃用) (C++20 中移除) | |||||||
noexcept(true)
noexcept
的 (
始终是此形式的一部分(它绝不能开始初始化器)。表达式 | - | 类型为 bool 的上下文转换常量表达式 |
[编辑] 解释
noexcept-specification 不属于函数类型的一部分(就像 动态异常规范 一样),并且只能作为 lambda 声明符 或顶级 函数声明符 的一部分出现,用于声明函数、变量、函数类型的非静态数据成员、函数指针、函数引用或成员函数指针,以及在这些声明中声明参数或返回类型时,如果该参数或返回类型恰好是函数指针或引用。它不能出现在 typedef 或 类型别名 声明中。 void f() noexcept; // the function f() does not throw void (*fp)() noexcept(false); // fp points to a function that may throw void g(void pfa() noexcept); // g takes a pointer to function that doesn't throw // typedef int (*pf)() noexcept; // error |
(C++17 前) |
noexcept-specification 是函数类型的一部分,可以作为任何 函数声明符 的一部分出现。 |
(C++17 起) |
C++ 中的每个函数要么是*不抛出*的,要么是*可能抛出*的
- *可能抛出*的函数是
|
(C++17 前) |
- 声明了
noexcept
说明符且其 表达式 求值为false
的函数 - 未声明
noexcept
说明符的函数,但以下情况除外:
- 声明了
|
(C++20 起) |
- 不抛出函数是所有其他函数(那些带有 noexcept 说明符且其 表达式 求值为
true
的函数,以及析构函数、默认的特殊成员函数和解分配函数)
显式实例化可以使用 noexcept 说明符,但不是必需的。如果使用,异常规范必须与其他所有声明相同。仅当在单个翻译单元中异常规范不相同时才需要诊断。
仅在异常规范上有所不同的函数不能重载 (就像返回类型一样,异常规范是函数类型的一部分,而不是函数签名的一部分)(C++17 起)。
void f() noexcept; void f(); // error: different exception specification void g() noexcept(false); void g(); // ok, both declarations for g are potentially-throwing
指向不抛出函数的指针(包括成员函数指针)可以赋值给或用于初始化(C++17 前)可 隐式转换为(C++17 起) 指向可能抛出函数的指针,但反之则不行。
void ft(); // potentially-throwing void (*fn)() noexcept = ft; // error
如果一个虚函数是不抛出异常的,那么其所有声明,包括每个重写器的定义,也必须是不抛出异常的,除非重写器被定义为已删除
struct B { virtual void f() noexcept; virtual void g(); virtual void h() noexcept = delete; }; struct D: B { void f(); // ill-formed: D::f is potentially-throwing, B::f is non-throwing void g() noexcept; // OK void h() = delete; // OK };
不抛出函数允许调用可能抛出函数。每当抛出异常并且处理器的搜索遇到不抛出函数的最外层块时,函数 std::terminate 被调用
extern void f(); // potentially-throwing void g() noexcept { f(); // valid, even if f throws throw 42; // valid, effectively a call to std::terminate }
函数模板特化版本的异常规范不会与函数声明一起实例化;它只在*需要*时(如下所述)才实例化。
隐式声明的特殊成员函数的异常规范也仅在需要时才评估(特别是,派生类成员函数的隐式声明不需要实例化基类成员函数的异常规范)。
当函数模板特化版本的 noexcept-specification *需要*时,但尚未实例化,则会查找依赖名称,并实例化 表达式 中使用的所有模板,如同用于特化版本的声明一样。
在以下语境中,函数的 noexcept-specification 被认为是*必需的*
- 在表达式中,函数通过重载决议被选择
- 该函数被 ODR 使用
- 该函数将被 ODR 使用,但出现在未求值的操作数中
template<class T> T f() noexcept(sizeof(T) < 4); int main() { decltype(f<void>()) *p; // f unevaluated, but noexcept-spec is needed // error because instantiation of the noexcept specification // calculates sizeof(void) }
- 需要此规范以与另一个函数声明进行比较(例如,在虚函数重写器或函数模板的显式特化上)
- 在函数定义中
- 需要该规范,因为默认的特殊成员函数需要检查它以确定其自身的异常规范(这仅在默认的特殊成员函数的规范本身被需要时发生)。
*可能抛出表达式*的形式定义(用于确定析构函数、构造函数和赋值运算符的默认异常规范,如上所述)
表达式 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; // implicit exception specification is 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() potentially-throwing because of the new operator // D::D(const D&) non-throwing // D::D(D&&) potentially-throwing: the default argument for B’s constructor may throw // D::~D() potentially-throwing // note; if A::~A() were virtual, this program would be ill-formed because an overrider // of a non-throwing virtual cannot be potentially-throwing };
[编辑] 注意
常量 表达式 的用途之一(与 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, throw(C++17 起)(C++20 前)
[编辑] 示例
// whether foo is declared noexcept depends on if the expression // T() will throw any exceptions template<class T> void foo() noexcept(noexcept(T())) {} void bar() noexcept(true) {} void baz() noexcept { throw 42; } // noexcept is the same as noexcept(true) int main() { foo<int>(); // noexcept(noexcept(int())) => noexcept(true), so this is fine bar(); // fine baz(); // compiles, but at runtime this calls std::terminate }
[编辑] 缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
CWG 1330 | C++11 | 异常规范可能被急切实例化 | 它仅在需要时才实例化 |
CWG 1740 | C++11 | 紧跟 noexcept 的 ( 可能开始一个初始化器 | 它只能是 noexcept 规范的一部分 |
CWG 2039 | C++11 | 只需要转换前的表达式是常量 | 转换也必须 在常量表达式中有效 |
[编辑] 参阅
noexcept 运算符(C++11) |
确定表达式是否抛出任何异常 |
动态异常规范(C++17 前) | 指定函数抛出哪些异常 (C++11 中废弃) |
throw 表达式
|
发出错误信号并将控制转移给错误处理器 |
(C++11) |
如果移动构造函数不抛出异常,则将参数转换为亡值 (函数模板) |