必须加noexcept的情况包括:标准库容器扩容时移动操作非noexcept会退化为复制;自定义移动操作逻辑上不抛异常时须显式声明;析构函数默认noexcept,手动实现不可抛异常。

加 noexcept 不一定优化,但不加可能阻碍优化——尤其在移动操作、容器重分配、栈展开路径等关键位置。
什么时候必须加 noexcept
标准库容器(如 std::vector)在扩容时,若元素的移动构造函数或移动赋值运算符不是 noexcept,会退回到复制而非移动,性能直接降一档。C++11 起,std::move_if_noexcept 和容器的 reserve/resize 都依赖这个契约。
-
std::vector移动元素前会检查::push_back std::is_nothrow_move_constructible_v - 自定义类型若提供移动操作,且逻辑上绝不抛异常,必须显式声明为
noexcept - 析构函数默认是
noexcept的;若手动写了析构函数又抛了异常,程序直接调用std::terminate
noexcept 与 noexcept(true)、noexcept(false) 的区别
三者语义不同,不能混用:noexcept 是 noexcept(true) 的简写,而 noexcept(false) 明确禁止编译器做任何异常安全假设。
-
void f() noexcept;→ 等价于void f() noexcept(true);,违反则调用std::terminate -
void g() noexcept(false);→ 显式声明可能抛异常,编译器不会做noexcept相关优化 -
void h() noexcept(noexcept(expr));→ 条件 noexcept(如转发函数),括号内是常量表达式,用于模板推导
容易踩的坑:隐式异常和模板推导
看似不抛异常的函数,可能因调用链中某个函数没标 noexcept 而“污染”整个声明。更隐蔽的是模板函数——编译器无法自动推导是否 noexcept,必须显式写。
立即学习“C++免费学习笔记(深入)”;
- 成员函数调用了
std::string::append?它不是noexcept(可能抛std::bad_alloc),所以你的函数也不能标noexcept - 模板移动构造函数:
template才能正确传递异常规格X(T&& t) noexcept(noexcept(T(std::move(t)))) - lambda 默认不是
noexcept,即使函数体为空;需写[]() noexcept {}
编译器实际怎么用 noexcept
它不是给运行时看的,而是给编译器优化器和标准库元函数用的信号。现代编译器(GCC/Clang/MSVC)在启用优化(-O2 及以上)时,会据此省略栈展开表(stack unwinding tables)、内联判断、以及选择更激进的代码路径。
- 未开启异常支持(如
-fno-exceptions)时,noexcept失去意义,所有函数都被视为noexcept(true) - 即便函数标了
noexcept,若内部调用了一个未标noexcept的函数,链接期或运行期仍可能崩溃(取决于调用是否真发生异常) - 调试构建中,某些编译器会忽略
noexcept检查;发布构建才真正生效
最常被忽略的一点:noexcept 是接口契约的一部分,一旦加了就不能随便去掉——哪怕函数体没变,下游模板实例化可能已依赖该信息,修改会导致 ODR 违规或静默行为变化。









