noexcept关键字承诺函数不抛异常,若违反则调用std::terminate;它支持编译器优化、提升移动操作性能,并保障析构函数等关键操作的异常安全,常用于泛型编程中通过noexcept操作符和SFINAE选择最优重载。
在c++中,noexcept关键字用于指定一个函数不会抛出任何异常。它既是声明也是承诺:如果函数被标记为 noexcept
但实际抛出了异常,程序将直接调用 std::terminate()
终止运行。这个机制不仅增强了代码的异常安全性,也提供了优化机会。
noexcept 的基本语法与含义
noexcept
可以作为函数声明的一部分,出现在函数参数列表之后:
void func() noexcept; // 承诺不抛异常
void func() noexcept(true); // 等价于上面
void func() noexcept(false); // 允许抛异常
其中 noexcept
等同于 noexcept(true)
,表示该函数不会抛出异常;而 noexcept(false)
表示可能抛出异常。
也可以使用表达式来动态决定是否为 noexcept
:
立即学习“C++免费学习笔记(深入)”;
template<typename T>
void swap(T& a, T& b) noexcept(noexcept(a.swap(b)));
这里的外层 noexcept
根据内层表达式是否可能抛异常来决定当前函数是否标记为 noexcept
。这种写法常用于泛型编程中保持异常安全。
提高性能与编译器优化
当编译器知道某个函数不会抛出异常时,可以省略生成相关的异常处理表(如栈展开信息),从而减少二进制体积并提升执行效率。
例如,在移动构造函数或移动赋值操作中标记 noexcept
非常关键。标准库容器(如 std::vector
)在重新分配内存时,优先使用 noexcept
的移动构造函数,否则会退化为复制操作以保证异常安全。
以下是一个典型例子:
class MyType {
public:
MyType(MyType&& other) noexcept { /* 移动资源 */ }
};
若未标记 noexcept
,std::vector<MyType>
在扩容时可能选择复制而非移动,导致性能下降。
异常安全保证与程序稳定性
noexcept
是实现强异常安全(Strong Exception Safety)的重要工具。某些操作要求必须不能抛异常,比如析构函数、释放资源的函数等。C++标准规定,如果析构函数抛出异常且未被捕获,会导致程序终止。
因此,良好的实践是将析构函数显式标记为 noexcept
(即使不写,默认也是 noexcept
):
~MyClass() noexcept { }
此外,像 std::swap
、std::move
这类基础操作通常期望是 noexcept
的,以便上层算法能安全高效地使用它们。
noexcept 与类型系统和 SFINAE
C++11 引入了 noexcept
操作符,可用于判断表达式是否会抛异常:
bool isNoexcept = noexcept(func()); // 返回 true 或 false
这在模板元编程中有重要作用。结合 enable_if
,我们可以根据函数是否 noexcept
来启用不同的重载版本:
template<typename T>
typename std::enable_if_t<noexcept(std::declval<T>().swap(std::declval<T>()))>
swap_safe(T& a, T& b) { a.swap(b); }
这种方式允许我们编写更智能、更高效的泛型代码。
基本上就这些。合理使用 noexcept
能提升程序性能、增强异常安全,并帮助标准库做出最优选择。不过要注意:一旦标记为 noexcept
,就不能再抛异常,否则程序直接终止,所以务必确保逻辑正确。