C++ noexcept关键字 C++ 声明函数不抛异常的优化【规范】

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

noexcept 不一定优化,但不加可能阻碍优化——尤其在移动操作、容器重分配、栈展开路径等关键位置。

什么时候必须加 noexcept

标准库容器(如 std::vector)在扩容时,若元素的移动构造函数或移动赋值运算符不是 noexcept,会退回到复制而非移动,性能直接降一档。C++11 起,std::move_if_noexcept 和容器的 reserve/resize 都依赖这个契约。

  • std::vector::push_back 移动元素前会检查 std::is_nothrow_move_cons

    tructible_v
  • 自定义类型若提供移动操作,且逻辑上绝不抛异常,必须显式声明为 noexcept
  • 析构函数默认是 noexcept 的;若手动写了析构函数又抛了异常,程序直接调用 std::terminate

noexceptnoexcept(true)noexcept(false) 的区别

三者语义不同,不能混用:noexceptnoexcept(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,必须显式写。

  • 成员函数调用了 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 违规或静默行为变化。

猜你喜欢

联络方式:

400 9058 355

邮箱:8955556@qq.com

Q Q:8955556

微信二维码
在线咨询 拨打电话

电话

400 9058 355

微信二维码

微信二维码