异常抛出性能开销远大于捕获,主因是栈展开:回溯调用栈、调用析构函数、动态查找catch;try块无异常时近乎零开销;应仅用于真正异常场景,高频抛出严重损害性能。

异常抛出时的性能开销远大于捕获
真正拖慢程序的不是 try 块本身,而是执行 throw 时触发的栈展开(stack unwinding):编译器必须回溯调用栈,为每个作用域内已构造的对象调用析构函数,并查找匹配的 catch。这个过程涉及动态查找、内存访问、虚表跳转(如涉及 std::exception 多态),在深度调用链中可能耗时数百纳秒到微秒级——远超一次普通函数调用。
而 try 块在无异常路径下几乎零开销(现代编译器如 GCC/Clang 默认启用 -fexceptions 时,仅增加极小的元数据,不插入运行时检查指令)。
- 高频抛出(如用异常做流程控制)会彻底破坏性能,等效于隐式
longjmp+ 析构调度 - 异常只应在“真正异常”的场景使用:文件打开失败、网络断连、解析错误等不可预期且无法局部恢复的情况
- 若函数逻辑上“可能失败但属正常路径”,应改用返回码(
std::expected、std::optional或自定义状态类型)
noexcept 不只是声明,它影响 ABI 和优化机会
noexcept 的关键作用不是“禁止抛出”,而是向编译器承诺“绝不会传播异常”。这个承诺让编译器能做两件事:一是省略该函数调用周围的异常处理元数据;二是启用某些激进优化(比如移动操作的自动选择)。
例如 std::vector::resize() 在元素类型移动构造函数标记为 noexcept 时,才敢用移动而非拷贝来重排内存——否则必须预留异常安全的回滚逻辑,性能直接打五折。
立即学习“C++免费学习笔记(深入)”;
- 所有移动构造/赋值函数,只要不抛出,务必显式写
T(T&&) noexcept - 不要给可能调用未知第三方代码的函数加
noexcept,一旦违反(抛出异常),程序直接调用std::terminate(),无任何调试提示 -
noexcept(expr)是编译期判断:比如noexcept(std::is_nothrow_move_constructible_v比硬写) noexcept更安全
零成本异常(zero-cost exceptions)的真实含义
“零成本”指**无异常发生时无运行时开销**,不是“异常发生时也零成本”。它的实现依赖两个前提:一是异常元数据(如 .eh_frame 段)静态生成,不占运行时 CPU;二是栈展开逻辑由编译器生成,不依赖运行时库介入(除非需要调用析构函数)。
但代价依然存在:可执行文件体积增大(尤其模板频繁实例化时)、L1/L2 缓存压力上升、部分嵌入式平台(如裸机 ARM Cortex-M)因缺乏 .eh_frame 解析支持而根本禁用异常。
- 启用
-fno-exceptions后,throw/catch变成编译错误,noexcept退化为注释(但noexcept(false)仍合法) - Linux x86_64 上,
throw平均比return -1慢 100–500 倍;macOS 使用 DWARF 展开机制,开销略低但依然显著 - 游戏引擎、高频交易、实时音频处理等场景普遍禁用异常,靠静态分析 + 断言 + 错误码保障健壮性
如何实测你代码里的异常开销?
别猜,用工具看。最直接方式是隔离 throw 路径并计时:
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 100000; ++i) {
try {
throw std::runtime_error("test");
} catch (const std::exception&) {}
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Avg throw+catch: "
<< std::chrono::duration_cast(end - start).count() / 100000.0
<< " ns\n"; 注意:确保编译时未开启 -fno-exceptions,且测试在相同优化等级(如 -O2)下进行。对比方案可以是等效的 std::optional 返回或全局错误码设置。
真正难评估的是间接成本:异常导致 CPU 分支预测失败、缓存行失效、以及迫使编译器放弃某些内联决策。这些在 microbenchmark 里看不到,但在长生命周期服务中会累积显现。











