C++关键字-noexcept
C++中的异常处理是在运行时而不是编译时检测的。为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化。 在实践中:
- 一个操作或者函数可能会抛出一个异常;
- 一个操作或者函数不可能抛出任何异常。
使用noexcept指定函数或操作不会抛出异常,会给编译器更大的优化空间,同时减少运行时开销。
当函数被标记为 noexcept 时,编译器可以进行以下优化:
- 避免生成额外的异常处理代码,从而减少代码大小和提高执行效率。
- 优化函数调用栈的管理,因为不需要为可能的异常保留额外的空间。
noexcept说明符
noexcept说明符指定函数是否会抛出异常。
noexcept用法
void func() noexcept { } // 1
void func() noexcept(expression) { } // 2
第2种写法将根据表达式的真假来判断函数是否抛出异常。 第1种写法等价于noexcept(true)
noexcept并不能保证函数不会抛出异常。如果函数在运行时仍然抛出了异常,那么程序会调用std::terminate()函数来终止程序的执行。
默认noexcept的函数
- 默认构造函数
- 默认拷贝构造函数
- 默认拷贝赋值函数
- 默认移动构造函数
- 默认移动赋值函数
- 析构函数
- delete运算符
注意:自定义析构函数带有noexcept,但自定义构造函数不带有noexcept。
什么时候应该使用noexcept
当为一个类设计移动系列(移动构造、移动复制)函数时,如果可以,最应该为其加上noexcept,以便此类在使用标准库容器时可以用移动操作来代替拷贝。
以vector为例:很多标准库容器需要特定时机来执行扩容操作(把元素从旧内存拷贝到新开辟的内存,再析构旧内存中的元素),针对这种情况明显可以用移动操作来优化。但标准库的做法是:如果容器中类的移动操作函数带有noexcept声明,则使用移动操作来代替拷贝;如果没有noexcept声明,则用拷贝来完成扩容。
noexcept运算符
noexcept运算符进行编译时检查,在表达式声明不会抛出任何异常的情况下返回true。
noexcept运算符是一个一元运算符,它的返回值是一个bool类型的右值常量表达式,用于表示给定的表达式是否会抛出异常。
#include <iostream>
#include <utility>
#include <vector>
void may_throw();
void no_throw() noexcept;
auto lmay_throw = []{};
auto lno_throw = []() noexcept {};
int main()
{
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'
}
输出:
may_throw() 可能会抛出异常吗?true
no_throw() 可能会抛出异常吗?false
lmay_throw() 可能会抛出异常吗?true
lno_throw() 可能会抛出异常吗?false
#include <iostream>
#include <utility>
#include <vector>
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
<< "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';
}
输出:
~T() 可能会抛出异常吗?false
T(T 右值) 可能会抛出异常吗?false
T(T 左值) 可能会抛出异常吗?false
U(U 右值) 可能会抛出异常吗?true
U(U 左值) 可能会抛出异常吗?true
V(V 右值) 可能会抛出异常吗?false
V(V 左值) 可能会抛出异常吗?true