C++ 异常处理怎么用?try/catch/noexcept 与异常安全详解
C++ 异常用 try/catch/throw 处理运行时错误。throw 抛出异常对象,catch 按类型匹配捕获,catch(...) 捕获所有异常。标准异常继承自 std::exception,分 logic_error(编程错误)和 runtime_error(运行时错误)两大分支。自定义异常继承 std::runtime_error 或 std::exception,重写 what() 返回错误信息。异常安全三个级别:基本保证(异常后对象仍有效)、强保证(异常后状态回滚)、不抛保证(noexcept)。RAII 是异常安全的基石——栈展开时局部对象的析构函数保证执行,智能指针和 lock_guard 就是典型应用。析构函数绝不能抛异常,否则栈展开期间会调 terminate。noexcept 告诉编译器函数不抛异常,vector 扩容时会根据移动构造是否 noexcept 决定用移动还是拷贝。
追问
析构函数为什么不能抛异常?
栈展开时如果析构函数再抛异常,C++ 运行时无法同时处理两个异常,直接调 std::terminate 程序崩溃。如果析构中调用的函数可能抛异常,用 try/catch 在析构函数内部吞掉。
catch 的匹配顺序是怎样的?
按出现顺序从上到下匹配,匹配第一个符合条件的 catch 就执行,不会找"最佳匹配"。所以要把最具体的异常类型放前面,std::exception 放后面,catch(...) 放最后。
noexcept 函数里抛了异常会怎样?
运行时调 std::terminate 直接结束程序,不会栈展开。所以给函数加 noexcept 要确认真的不会抛,或者内部全部 catch 了。但 noexcept 让编译器优化更激进,移动构造加 noexcept 后 vector 扩容会用移动而非拷贝,性能更好。
异常和错误码怎么选?
异常用于不可预期的错误(内存分配失败、文件打不开),错误码用于可预期的分支逻辑(查找失败、无效输入)。异常的缺点是有性能开销(栈展开、异常对象构造),且跨动态库边界可能出问题。性能敏感路径用 error_code 或 optional。