noexcept 是异常安全契约而非性能开关,声明函数绝不抛异常,影响优化、类型trait及abi;误标导致std::terminate,移动操作必须显式标注以避免容器降级为拷贝。

noexcept 用在函数声明上,不是性能开关,而是契约声明
它不加速代码,但让编译器知道“这个函数绝不会抛异常”,从而启用某些优化(比如移动构造时选择 std::move_if_noexcept 的分支),也影响类型 trait 判断(如 std::is_nothrow_move_constructible_v)。误标 noexcept 却实际抛异常,程序会直接调用 std::terminate,没有栈展开,调试极难定位。
- 只对确定不会抛异常的函数加
noexcept,包括空实现、纯计算、仅调用其他noexcept函数的组合 - 析构函数默认是隐式
noexcept,显式写出来更清晰;若内部可能抛异常,必须用noexcept(false) - 模板函数慎用
noexcept,除非能静态断言所有实例化路径都不抛,否则用noexcept(noexcept(expr))这种双重检查
移动操作加 noexcept 是强制约定,不是可选项
标准容器(如 std::vector)在扩容或重哈希时,优先使用移动而非拷贝——但前提是移动构造/赋值是 noexcept。否则退化为拷贝,性能断崖下跌,且可能破坏强异常安全保证。
- 自定义类的移动构造函数和移动赋值运算符,只要没做可能抛异常的操作(如 new 失败、文件 I/O),就该显式加
noexcept - 常见错误:移动中调用
std::String的非noexcept成员(如resize)、或未检查std::vector::reserve是否可能抛std::bad_alloc - 验证方式:
static_assert(std::is_nothrow_move_constructible_v<myclass>);</myclass>放在类定义后,CI 里跑起来
noexcept 表达式比 noexcept 声明更灵活,但也更易错
noexcept(expr) 是运行时决定是否声明为 noexcept 的语法,expr 必须是常量表达式,编译期可求值。它常用于模板推导,但容易因类型擦除或 SFINAE 失效导致误判。
- 典型用法:
noexcept(noexcept(f(x)))—— 外层noexcept是声明修饰符,内层是操作符,返回bool常量表达式 - 陷阱:如果
f(x)是重载函数集,noexcept(f(x))可能因 ADL 或模板参数推导失败而 SFINAE 掉,整个表达式变成false,即使你本意是想捕获某个可行重载 - 调试建议:把
noexcept(...)拆成独立constexpr变量,用static_assert打印值,避免静默失效
noexcept 对 ABI 有实际影响,跨模块时必须一致
带 noexcept 和不带的函数,是两个不同的 ABI 符号。windows 上表现为导出符号名不同;linux 上虽符号名相同,但 libstdc++ 和 libc++ 对 noexcept trait 的实现细节有差异,混用可能触发 ODR 违规。
立即学习“C++免费学习笔记(深入)”;
- 导出到 DLL 或共享库的函数,声明必须与实现完全一致;头文件里写了
noexcept,源文件就不能漏 - 第三方库头文件若没标
noexcept,你自己封装一层时别擅自加上,除非你 100% 确保封装逻辑不引入异常路径 - Clang/GCC 的
-fno-exceptions下,所有函数都隐式noexcept,但此时noexcept声明本身仍合法;不过若链接了含异常处理的库,行为未定义
最常被忽略的是:noexcept 不是“我保证不 throw”,而是“我承诺绝不让异常逃出函数边界”——哪怕内部 try/catch 吞掉异常,只要没 rethrow,也算满足。但吞异常再返回错误码,和标 noexcept 是两回事,得自己权衡语义一致性。