throw只能被动态作用域内最近的未退出try块捕获,否则触发std::terminate();catch(const std::exception&)比catch(...)更安全,因后者会吞掉非标准异常且无法访问e.what();析构函数绝不能throw,必须noexcept;跨线程传异常需用std::current_exception()和std::rethrow_exception()。

throw 和 catch 必须在同一个 try 块作用域内才有效
很多人写 throw 时以为只要后面有 catch 就能捕获,结果程序直接 terminate。根本原因是:异常只能被**动态作用域内最近的、尚未退出的 try 块**捕获。如果 throw 发生在函数里,而调用它的外层没包 try,异常就一路向上,直到栈展开完毕也没被捕获,触发 std::terminate()。
实操建议:
- 不要依赖“全局 catch”——C++ 没这东西;必须显式用
try包住可能抛异常的代码段 - 函数声明若标了
noexcept,里面再throw会立即调用std::terminate(),不是跳到外层catch - 跨线程抛异常无效:一个线程里的
throw不能被另一个线程的catch捕获,得用std::exception_ptr转移
catch(const std::exception& e) 为什么比 catch(...) 更安全
catch(...) 看似万能,但会吞掉所有异常类型,包括 int、char* 这类非标准异常,也拦不住系统信号(如 SIGSEGV)转换来的异常。更麻烦的是,它不提供异常对象,你连 e.what() 都调不了。
实操建议:
- 优先用
catch(const std::exception& e),它能捕获所有从std::exception派生的标准异常(如std::runtime_error、std::out_of_range) - 如果要兼容旧代码里乱 throw 的
int或字符串字面量,可以加一层catch(int)或catch(const char*),但务必放在std::exception之后(否则会被提前截胡) - 别写
catch(std::exception e)(传值):会触发拷贝构造,可能再次抛异常;必须用 const 引用
析构函数里 throw 会导致程序直接终止
当栈展开过程中,某个对象的析构函数抛出异常,而此时已有另一个未处理的异常正在传播(即“双重异常”),C++ 标准强制调用 std::terminate()。这是硬性限制,不是警告。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
实操建议:
- 析构函数必须是
noexcept(C++11 起默认隐含),显式写~T() noexcept更清晰 - 如果析构中真需要报告错误(比如关闭文件失败),改用日志或设置状态标志,**绝不要 throw**
- RAII 类设计时,把可能失败的操作(如 flush、sync)拆成普通成员函数,由用户显式调用并处理异常
std::current_exception() 是跨作用域传递异常的唯一可靠方式
想把异常从一个线程存起来,稍后在另一个线程重抛?或者想在 catch 块里记录日志后再继续传播?不能靠保存异常对象指针或引用——它们生命周期只在当前 catch 块内。
实操建议:
- 用
std::current_exception()获取std::exception_ptr,它能安全持有异常对象副本 - 传给其他线程后,用
std::rethrow_exception(eptr)重抛,此时类型和堆栈信息(如果编译器支持)都保留 - 注意:
std::exception_ptr为空时调用rethrow_exception会 terminate,使用前用if (eptr) { ... }判空
异常安全最难的不是语法,而是判断哪些函数该 noexcept、哪些资源必须用 RAII 封装、以及什么时候该让异常穿透而不是吞掉——这些决定一旦写死,后期很难重构。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










