noexcept是承诺不抛异常的契约声明,违背则调用std::terminate()终止程序;它影响移动语义、优化和容器行为,错误标注会导致静默性能下降或崩溃。

noexcept 不是“不抛异常”,而是“承诺不抛异常”
很多人以为 noexcept 是让编译器阻止函数抛异常,其实它只是个契约声明:你写了 noexcept,就等于告诉编译器“我保证运行时绝不会让异常逃出这个函数”。一旦违背(比如在 noexcept 函数里调了可能抛异常的代码又没捕获),程序会直接调用 std::terminate() —— 不是报错、不是栈回溯,是立刻终止。
常见错误现象:noexcept 函数里调用了未加防护的 std::vector::push_back()(可能因内存分配抛 std::bad_alloc),结果一触发就静默崩溃,调试时连异常栈都看不到。
- 使用场景:移动构造函数、移动赋值运算符、析构函数(C++11 起默认隐式
noexcept)必须严格满足,否则容器(如std::vector)可能退化为拷贝而非移动 - 参数差异:
noexcept本身无参数;noexcept(expr)是条件 noexcept(expr 为常量表达式,求值为 true 才生效),比如noexcept(noexcept(other_func())) - 性能影响:编译器对
noexcept函数可做更激进优化(如省略栈展开逻辑),且 STL 容器在判断能否移动时依赖它 —— 错标noexcept可能导致意外拷贝,拖慢性能
移动操作不加 noexcept,vector 扩容时可能悄悄降级为拷贝
这是最典型也最容易被忽略的实际后果。当 std::vector 需要扩容并重新安置元素时,它会优先尝试移动。但如果元素类型的移动构造函数没有标记 noexcept,标准规定它必须退回到拷贝构造 —— 即使你的移动函数实际上从不抛异常。
示例:MyClass 的移动构造函数没写 noexcept,哪怕内部只做指针交换:
立即学习“C++免费学习笔记(深入)”;
class MyClass {
std::string* data;
public:
MyClass(MyClass&& other) : data(other.data) { other.data = nullptr; }
// ❌ 缺少 noexcept → vector 扩容时拒绝移动
};- 正确写法:显式加上
noexcept:MyClass(MyClass&& other) noexcept : data(other.data) { other.data = nullptr; } - 兼容性影响:C++11/14/17 行为一致;但 C++20 开始,部分 STL 实现对
noexcept检查更严格(如std::optional构造) - 验证方式:用
static_assert(std::is_nothrow_move_constructible_v<myclass>)</myclass>在编译期确认
析构函数默认 noexcept,但显式 throw 就会崩
C++11 起,所有用户定义的析构函数默认带有隐式 noexcept(等价于写了 ~T() noexcept)。这意味着:如果析构函数里调用了可能抛异常的函数(比如某成员的析构函数没标 noexcept,或你自己写了 throw),程序会在该点直接终止。
常见错误现象:在析构函数里做日志、网络关闭、文件 flush 等 I/O 操作,而这些操作底层可能抛异常(如 std::ofstream::close() 失败时可能抛 std::ios_base::failure)。
- 安全做法:析构函数内一律避免抛异常;I/O 类操作改用返回错误码或忽略失败(如
try { file.close(); } catch (...) {}) - 例外情况:若确定某个析构函数必须抛异常(极罕见),可显式写成
~T() noexcept(false),但此时它不能再作为任何 STL 容器元素类型(编译失败) - 注意:基类析构函数是否
noexcept会影响派生类 —— 如果基类析构抛异常,派生类即使写了noexcept也会违反契约
noexcept 运算符:运行时无法检测,只能编译期查
noexcept 运算符(noexcept(expr))不是函数,它在编译期对 expr 做静态分析,返回 bool 常量表达式。它不执行 expr,也不关心运行时行为 —— 即使 expr 是个明显会崩溃的空指针解引用,只要编译器能静态判定“此处不会因异常退出”,noexcept(expr) 就返回 true。
典型误用:想用它检查某个函数调用“实际会不会抛”,比如 if (noexcept(f())) { /* safe */ } else { /* handle */ } —— 这毫无意义,因为分支在编译期就确定了,且 f() 的异常行为根本不在 noexcept 运算符的判定范围内(它只看函数声明中的 noexcept 规范)。
- 真实用途:模板元编程中做 SFINAE 分流,例如
template<typename t> auto foo(T&& t) -> decltype(t(), void()) noexcept(noexcept(t()))</typename> - 陷阱:不要对非声明可见的函数使用 —— 如果
f()声明没带noexcept,即使定义里没抛,noexcept(f())仍为false - 调试建议:用
std::is_nothrow_*_v系列 trait 比手写noexcept运算符更清晰、更可靠
复杂点在于:noexcept 是契约而非机制,它不改变运行时行为,只改变编译器和标准库的决策路径。写错的代价不是编译失败,而是静默的性能损失或不可预测的终止 —— 很多时候你得靠 static_assert 和容器行为反推,而不是靠报错提醒。









