noexcept
specifier
(since C++11)
指定函数是否可能抛出异常。
目录 |
语法
noexcept
|
(1) | ||||||||
noexcept(
expression
)
|
(2) | ||||||||
throw()
|
(3) |
(C++17 中弃用)
(C++20 中移除) |
|||||||
noexcept(true)
noexcept
后接的
(
始终属于此形式(绝不能作为初始化器的起始符号)。
| expression | - | 上下文转换的 bool 类型常量表达式 |
说明
|
noexcept 说明符不是函数类型的一部分(如同 动态异常说明 ),并且只能在声明函数、函数指针类型、函数引用类型、指向成员函数的指针类型的变量或非静态数据成员时,作为 lambda 声明符 或顶层 函数声明符 的一部分出现。它也可以在那些声明中作为参数或返回类型出现,当这些参数或返回类型本身恰好是函数指针或函数引用时。它不能出现在 typedef 或 类型别名 声明中。 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++中的每个函数要么是 非抛出 的,要么是 可能抛出 的:
- potentially-throwing 函数包括:
|
(直至 C++17) |
-
-
使用
noexcept说明符声明且其 表达式 求值为false的函数 -
未使用
noexcept说明符声明的函数,但以下情况除外:
-
使用
|
(C++20 起) |
-
非抛出函数包括所有其他函数(具有 noexcept 说明符且其
表达式
求值为
true的函数,以及析构函数、默认化的特殊成员函数和释放函数)
显式实例化 可以使用 noexcept 说明符,但这不是必需的。如果使用,异常规范必须与所有其他声明保持一致。仅当在同一翻译单元内的异常规范不一致时才需要产生诊断信息。
仅在异常规范上不同的函数不能重载 (与返回类型类似,异常规范是函数类型的一部分,但不是函数签名的一部分) (since 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; // 正确 void h() = delete; // 正确 };
非抛出函数允许调用可能抛出的函数。当异常被抛出且对处理器的搜索遇到非抛出函数的最外层块时,将调用函数 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 规范 // 错误,因为实例化 noexcept 规范 // 会计算 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; // 隐式异常规范为 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 , 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 后可能开始初始化器 | 只能作为异常规范的一部分 |
| CWG 2039 | C++11 | 仅要求转换前的表达式为常量 | 转换也必须在常量表达式中有效 |
参见
noexcept
operator
(C++11)
|
判断表达式是否抛出任何异常 |
| Dynamic exception specification (until C++17) | 指定函数抛出的异常类型 (C++11 中弃用) |
throw
expression
|
发出错误信号并将控制权转移给错误处理程序 |
|
(C++11)
|
如果移动构造函数不抛出异常,则将参数转换为 xvalue
(函数模板) |