noexcept操作符在编译期判断表达式是否声明为不抛异常,返回bool常量;必须写成noexcept(func())等可求值表达式形式,不可省略调用括号,常用于模板sfinae、移动语义优化及static_assert验证。

用 noexcept 操作符在编译期判断函数是否 noexcept
直接用标准提供的 noexcept 操作符,它会在编译期返回 true 或 false —— 不是运行时检查,也不依赖 RTTI。比如:noexcept(func()) 判断调用 func() 是否被声明为不抛异常。
注意:括号里必须是**可求值的表达式**,不是函数类型或指针。常见错误是写成 noexcept(func)(未加调用括号),这会触发 SFINAE 失败或编译错误,因为 func 本身不是表达式。
- 若
func是重载函数,需强制指定签名,例如:noexcept(static_cast<void>(func)())</void> - 若
func是成员函数,需绑定对象或使用std::declval,如:noexcept(std::declval<t>().member_func())</t> - lambda 默认是
noexcept(除非捕获抛异常的表达式),但显式标注[]() noexcept {}后才稳定可测
模板中根据 noexcept 特性做分支选择
结合 noexcept 操作符和 std::enable_if_t 或 C++20 的 requires,能实现编译期分发。典型场景是移动操作:只对 noexcept 的移动构造函数启用 std::vector 的快速重分配。
示例(C++17):
template<typename t>
auto move_if_noexcept(T&& t)
-> std::enable_if_t<noexcept t> {
return std::move(t);
}</noexcept></typename>
- 别漏掉
noexcept括号里的完整调用形式,否则模板推导失败 - 函数模板参数类型推导可能掩盖
noexcept差异,建议用decltype显式提取表达式类型再测 - 类模板特化时,
noexcept判断应放在偏特化条件里,而非主模板内部
静态断言验证 noexcept 声明是否生效
用 static_assert 确保关键函数确实被标记为 noexcept,避免因忘记更新声明导致意外异常传播。比如容器的析构函数、交换函数通常要求 noexcept。
示例:
struct MyContainer {
~MyContainer() noexcept; // 声明
};
static_assert(noexcept(std::declval<mycontainer>().~MyContainer()),
"MyContainer destructor must be noexcept");</mycontainer>
- 析构函数默认是
noexcept,但一旦其成员有非noexcept析构,整个类析构就自动变成可能抛异常 —— 必须显式写noexcept并用static_assert锁死 - 对
constexpr函数,noexcept是隐式添加的,但显式写出更清晰,且便于断言 - 跨平台时注意:MSVC 对某些隐式
noexcept推导比 Clang/GCC 更保守,断言能提前暴露差异
调试时如何确认编译器实际采用了 noexcept 优化
编译器不会输出“已启用 noexcept 优化”的日志,但可通过生成代码反推:如果函数被标记为 noexcept,编译器通常省略异常栈展开逻辑(如 .eh_frame 条目减少、无 call __cxa_begin_catch 等)。
实操方法:
- 用
objdump -d或clang -S -O2查看汇编,对比有无异常相关指令(如test+je跳转到__cxa_throw) - 链接时加
-Wl,--no-as-needed防止异常支持库被裁剪,否则可能误判 - 对内联函数,
noexcept影响的是调用点行为,不是函数体本身 —— 所以要检查调用方汇编,而非函数定义处
真正难的是跨翻译单元的传播:一个函数在 A.cpp 标了 noexcept,但 B.cpp 通过头文件包含调用时,若头文件没重新编译,旧 object 文件仍按老签名链接 —— 这类问题只能靠 CI 强制 clean build 加 static_assert 捕获。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











