noexcept

noexcept 说明符

指定函数是否抛出异常。

语法 说明
noexcept 与 noexcept(true) 相同
noexcept(表达式) 如果 表达式 求值为 true,那么声明函数不会抛出任何异常。后随 noexcept 的 ( 只能是该形式的一部分(它不是初始化器的开始)。

noexcept 说明不是函数类型的一部分(正如同动态异常说明),而且只能在声明函数、变量、函数类型的非静态数据成员、函数指针、函数引用或成员函数指针时,以及在以上这些声明中声明类型为函数指针或函数引用的形参或返回类型时,作为 lambda 声明符或顶层函数声明符的一部分出现。它不能在 typedef 或类型别名声明中出现。

C++ 中的每个函数要么不会抛出,要么有可能会抛出。
有可能会抛出的函数是:

  • 声明带有求值为 false 的 表达式 的 noexcept 说明符的函数
  • 声明不带有 noexcept 声明的函数,除了
    • 析构函数,除非有任何可能在构造的基类或成员的析构函数有可能会抛出
    • 隐式声明的或在它的首个声明被预置的默认构造函数、复制构造函数、移动构造函数,除非
      • 由构造函数的隐式定义所调用的某个基类或成员的构造函数有可能会抛出
      • 这种初始化的某个子表达式,例如默认实参表达式,有可能会抛出
      • (默认构造函数的)默认成员初始化器有可能会抛出
    • 隐式声明的或在它的首个声明被预置的复制赋值运算符、移动赋值运算符,除非隐式定义中对任何赋值运算符的调用有可能会抛出
    • 解分配函数
  • 所有其他函数(以求值为 true 的 表达式 的 noexcept 说明符声明的函数,以及析构函数、预置的特殊成员函数和解分配函数)都不会抛出
1
2
3
4
void f() noexcept;
void f(); // 错误:异常说明不同
void g() noexcept(false);
void g(); // OK:g 的两个声明均有可能会抛出

如果虚函数不会抛出,那么它每个覆盖的函数的所有声明(包括定义)都必须不抛出,除非覆盖函数被定义为弃置:

1
2
3
4
5
6
7
8
9
10
11
12
13
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
};

noexcept 运算符

noexcept 运算符进行编译时检查,如果表达式不会抛出任何异常则返回 true。

它可用于函数模板的 noexcept 说明符中,以声明函数将对某些类型抛出异常,但不对其他类型抛出。

语法
noexcept( 表达式 )
返回 bool 类型的纯右值。

解释
noexcept 运算符不会对 表达式 求值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 示例
#include <iostream>
#include <utility>
#include <vector>

void may_throw();
void no_throw() noexcept;
auto lmay_throw = []{};
auto lno_throw = []() noexcept {};

class T
{
public:
~T(){} // 析构函数妨碍了移动构造函数
// 复制构造函数不会抛出异常
};

class U
{
public:
~U(){} // 析构函数妨碍了移动构造函数
// 复制构造函数可能会抛出异常
std::vector<int> v;
};

class V
{
public:
std::vector<int> v;
};

int main()
{
T t;
U u;
V v;

std::cout << std::boolalpha
<< "may_throw() 可能会抛出异常吗?" << !noexcept(may_throw()) << '\n'
<< "no_throw() 可能会抛出异常吗?" << !noexcept(no_throw()) << '\n'
<< "lmay_throw() 可能会抛出异常吗?" << !noexcept(lmay_throw()) << '\n'
<< "lno_throw() 可能会抛出异常吗?" << !noexcept(lno_throw()) << '\n'
<< "~T() 可能会抛出异常吗?" << !noexcept(std::declval<T>().~T()) << '\n'
// 注:以下各项测试也要求 ~T() 不会抛出异常
// 因为 noexcept 中的表达式会构造并销毁临时量
<< "T(T 右值) 可能会抛出异常吗?" << !noexcept(T(std::declval<T>())) << '\n'
<< "T(T 左值) 可能会抛出异常吗?" << !noexcept(T(t)) << '\n'
<< "U(U 右值) 可能会抛出异常吗?" << !noexcept(U(std::declval<U>())) << '\n'
<< "U(U 左值) 可能会抛出异常吗?" << !noexcept(U(u)) << '\n'
<< "V(V 右值) 可能会抛出异常吗?" << !noexcept(V(std::declval<V>())) << '\n'
<< "V(V 左值) 可能会抛出异常吗?" << !noexcept(V(v)) << '\n';
}

输出:

may_throw() 可能会抛出异常吗?true
no_throw() 可能会抛出异常吗?false
lmay_throw() 可能会抛出异常吗?true
lno_throw() 可能会抛出异常吗?false
~T() 可能会抛出异常吗?false
T(T 右值) 可能会抛出异常吗?false
T(T 左值) 可能会抛出异常吗?false
U(U 右值) 可能会抛出异常吗?true
U(U 左值) 可能会抛出异常吗?true
V(V 右值) 可能会抛出异常吗?false
V(V 左值) 可能会抛出异常吗?true

noexcept 修饰符有两种形式:

  • 一种就是简单地在函数声明后加上 noexcept 关键字,表示其修饰的函数不会抛出异常。 比如: void excpt_func() noexcept;
  • 另外一种则可以接受一个常量表达式作为参数: void excpt_func() noexcept ( 常量表达式 );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <iostream>
using namespace std;

void Throw() { throw 1; }

void NoBlockThrow() { Throw(); }

// 在 C++11 中使用 noexcept 可以有效地阻止异常的传播与扩散
void BlockThrow() noexcept { Throw(); } // 会直接调用 std::terminate 中断程序的执行,从而阻止了异常的继续传播

// TODO: 还不知道怎么用
// fun函数是否是一个noexcept的函数,将由T()表达式是否会抛出异常所决定。
// 这里的第二个noexcept就是一个noexcept操作符。当其参数是一个有可能抛出异常的表达式的时候,其返回值为false,反之为true
template<typename T>
void func(T) noexcept(noexcept(T())) {}




struct A {
// C++11默认将delete函数设置成noexcept,就可以提高应用程序的安全性
// ~A() noexcept(false) { throw 1; }
~A() { throw 1; } // 崩溃
};

struct B {
~B() noexcept(false) { throw 2; }
};

struct C {
B b;
};

void funcA() { A a; }
void funcB() { B b; }
void funcC() { C c; }

//#define run1
#define run2

int main()
{
#ifdef run1
try {
Throw();
}
catch (...) {
cout << "Found throw." << endl;
}

try
{
NoBlockThrow();
}
catch (...)
{
cout << "Throw is not blocked." << endl;
}

func(1);
try {
BlockThrow();
}
catch(...) {
cout << "Found throw 1." << endl;
}


#elif defined(run2)


try {
funcB();
}
catch(...) {
cout << "caught funcB." << endl;
}

try {
funcC();
}
catch(...) {
cout << "caught funcC." << endl;
}

try {
funcA();
}
catch(...) {
cout << "caught funcA." << endl;
}

#endif

return 0;
}