常见原因在于异常类型不匹配:C++要求严格类型匹配,const/volatile修饰符或引用符号缺失均导致捕获失败;应始终使用catch(const std::exception& e),避免传值或非常量引用。

catch 捕不到 std::exception 的常见原因
不是 catch 写错了,而是抛出的异常类型和 catch 声明的类型不匹配。C++ 异常捕获是严格类型匹配的,哪怕只是 const/volatile 修饰符不一致,或者缺少引用符号,都会跳过该 catch 块。
常见错误现象:throw std::runtime_error("msg") 却用 catch (std::exception e)(传值)——这会触发拷贝构造,而如果拷贝构造函数被声明为 explicit 或不可访问(极少见),或更常见的是:抛出的是派生类对象,但 catch 用非引用/非 const 引用接收,导致切片或编译失败。
- 总是用
catch (const std::exception& e),不是catch (std::exception e),也不是catch (std::exception& e) - 如果不确定异常类型,加一个兜底
catch (...),但它无法获取异常信息,只能用于日志或终止前清理 - 自定义异常类必须公有继承
std::exception,否则catch (const std::exception&)捕不到
try-catch 在构造函数里怎么写才安全
构造函数里抛异常是合法且常见的(比如资源获取失败),但必须注意:已构造完成的成员会被自动析构,而未开始构造的成员不会析构。问题在于,如果你在构造函数体里手动 new 了内存、打开了文件,又没用 RAII 封装,就容易泄漏。
使用场景:初始化列表中调用可能抛异常的函数(如 std::ifstream 打开失败)、或构造函数体内做校验失败。
立即学习“C++免费学习笔记(深入)”;
- 优先把资源获取逻辑放进 RAII 类型(如
std::unique_ptr、std::fstream),它们的构造函数抛异常时能自动回滚已构造部分 - 不要在构造函数里裸写
new+ try-catch,改用std::make_unique,它要么成功,要么抛异常,不会留下裸指针 - 如果必须手动管理,确保所有可能抛异常的操作都在 try 块内,且 catch 中只做清理、不抛新异常(否则会调用
std::terminate)
为什么 noexcept 函数里 throw 会导致程序终止
声明为 noexcept 的函数承诺不抛异常;一旦违反,C++ 标准规定直接调用 std::terminate,不会进入任何 catch 块。这不是 bug,是设计使然——编译器可能据此做激进优化(比如省略栈展开逻辑)。
性能影响明显:带 noexcept 的移动构造函数能被 std::vector 等容器优先选用;但若内部误 throw,运行时直接崩,毫无缓冲。
- 第三方库函数(如
std::vector::push_back)是否noexcept取决于其元素类型的移动操作是否noexcept,别盲目假设 - 自己写
noexcept函数前,确认所有调用链(包括间接调用)都真的不会抛——可以用noexcept(expr)检查表达式 - 调试时可临时删掉
noexcept,让异常浮上来定位问题,再决定是修复逻辑还是换异常安全策略
std::set_terminate 和 catch(...) 的实际分工
catch(...) 是局部防御,处理你知道可能发生异常的代码段;std::set_terminate 是全局兜底,只在“本不该发生却发生了”的情况下触发(比如析构函数意外抛异常、noexcept 违反、异常未被捕获)。它不能恢复执行,只能记录日志、生成 core dump、然后退出。
容易踩的坑:有人以为设了 std::set_terminate 就不用写 catch,结果线上 crash 日志里只有 “terminate called”,完全看不到原始异常类型和消息。
-
std::set_terminate回调函数里不要再 throw,也不要调用可能抛异常的 STL 函数(如std::cout 不安全,改用 <code>write(2)或std::fputs) - 真正有用的异常信息来自
std::current_exception(),但它只能在 active exception context 中调用(即正在栈展开时),所以catch(...)里保存比set_terminate里抓更可靠 - 多线程下
std::set_terminate是进程级的,一个线程崩溃会拖垮整个进程——这意味着你得确保每个线程入口都有自己的 try-catch 主循环











