【C++】noexcept的作用
noexcept是C++11引入的关键字,用于指定函数是否会抛出异常。它既是一个修饰符也是一个操作符,在现代C++编程中扮演着重要角色。
一、noexcept的基本概念
noexcept主要有两种形式:
- 无条件形式:void func() noexcept; 表示函数func保证不会抛出任何异常
- 条件形式:void func() noexcept(expression); 根据括号内的表达式结果决定是否抛出异常。如果表达式为true,表示不抛出异常;为false则表示可能抛出异常。
不带条件的noexcept等同于noexcept(true)。当标记为noexcept的函数确实抛出了异常,程序会立即调用std::terminate()终止运行,不会保证调用对象的析构函数。
二、noexcept的主要作用
1. 性能优化
编译器可以利用noexcept信息进行多种优化:
- 避免生成额外的异常处理代码,减少代码大小
- 优化函数调用栈的管理,不需要为可能的异常保留额外空间
- 标准库容器(如std::vector)会根据元素类型的移动操作是否为noexcept来决定使用移动还是拷贝操作
2. 代码可靠性与安全性
- 明确表明函数不会抛出异常,有助于代码审查和静态分析
- 减少程序崩溃风险,因为如果noexcept函数抛出异常,程序会立即终止
- 阻止异常的传播与扩散,避免异常在调用栈中向上传递
3. 影响函数重载和模板特化
noexcept可以作为函数类型的一部分,影响函数重载决策和模板特化:
void foo() noexcept;
void foo() noexcept(false);
编译器可能优先选择noexcept版本
三、noexcept的典型使用场景
1. 移动构造函数和移动赋值运算符
标准库容器在重新分配内存时,如果移动操作是noexcept,会优先使用移动而非拷贝.
2. 析构函数
析构函数通常不应抛出异常,默认就是noexcept的,除非显式声明为noexcept(false)
3.交换(swap)函数
交换操作通常不应抛出异常,可以标记为noexcept
4.性能关键的函数
在性能敏感的代码中,标记为noexcept可以帮助编译器优化
5.递归函数
递归函数声明为noexcept可以避免多次调用时的异常处理开销
四、noexcept操作符
noexcept还可以作为操作符使用,返回一个布尔值,表示表达式是否会抛出异常:
noexcept(expr) // 如果expr不会抛出异常则返回true,否则返回false
这在模板编程中特别有用,可以根据类型特性动态决定函数是否为noexcept。
五、注意事项
1. 谨慎使用
错误标记noexcept可能导致程序在异常情况下直接终止,难以调试
2. 异常安全
即使函数标记为noexcept,也应确保其实现是异常安全的,使用RAII技术管理资源
3. 不要过度承诺
只在确定函数不会抛出异常时才使用noexcept