c++ noexcept和throw()有什么区别 c++异常规范演进【详解】

noexcept 是编译期无开销的异常承诺,throw() 是运行期有开销且已废弃的动态规范;前者直接 terminate,后者调用 unexpected;noexcept 支持常量表达式和编译时探测,影响重载、优化及标准库实现。

noexcept 和 throw() 的核心区别在于:前者是编译期、无开销、语义明确的异常承诺;后者是运行期、有开销、已被淘汰的动态异常规范。

语法与语义本质不同

throw() 是 C++98 引入的动态异常规范,写在函数声明末尾,表示“该函数不抛出任何异常”。但它本质是运行时检查机制——若违反,先调用 std::unexpected(),再可能终止程序。

noexcept 是 C++11 引入的关键字,本身是编译期常量表达式。noexcept 或 noexcept(true) 表示“绝不抛出”,一旦抛出,直接调用 std::terminate(),跳过所有异常栈展开逻辑。

  • throw() 是历史遗留写法,C++17 起已正式废弃,C++20 中完全移除(仅保留 throw() 作为 noexcept(true) 的兼容别名)
  • noexcept 可带参数:noexcept(expr) 中的 expr 必须是编译期可判定的常量表达式,例如 noexcept(std::is_nothrow_move_constructible_v)
  • noexcept 运算符(如 noexcept(func()))用于编译时探测表达式是否可能抛出,返回布尔值,不执行 func()

性能与优化能力差异显著

throw() 声明无法被编译器可靠信任——它不参与重载决议,也不影响内联或调用约定选择,因为实际行为只能在运行时验证。

noexcept 提供强契约保证,编译器可据此激进优化:

  • 移动操作(如 vector 扩容)会优先选用 noexcept 的移动构造/赋值函数;否则退回到更慢的拷贝路径
  • 某些 ABI(如 Itanium)对 noexcept 函数使用更紧凑的调用约定,省去异常处理元数据
  • 模板元编程中可基于 noexcept 特性做 SFINAE 或 if constexpr 分支

标准库与现代实践全面转向 noexcept

C++11 后,标准库中几乎所有原本用 throw() 声明的函数(如 operator new、析构函数、swap 等)都已改用 noexcept。例如:

  • C++98:void* operator new(std::size_t) throw(std::bad_alloc);
  • C++11+:void* operator new(std::size_t) noexcept(false);(显式允许抛出)
  • 移动构造函数默认是 noexcept 的,除非成员中存在非 noexcept 的移动操作

用户自定义类型中,若希望 move 操作被容器高效利用,必须显式标注 noexcept,否则 std::vector 在 resize 时将避免移动而选择拷贝。

错误处理行为不可逆地简化

throw() 允许用户通过 set_unexpected 自定义 std::unexpected 处理器,带来调试灵活性但也引入不确定性;noexcept 彻底放弃这一层,抛出即 terminate——这是有意为之的设计取舍:用确定性换简洁性和性能。

这意味着 noexcept 更适合系统级、实时或资源敏感场景,而 throw() 的“可干预”特性反而成了维护负担和安全盲区。