异常处理的基本结构
C++中的异常处理机制通过try、catch和throw三个关键字实现。当程序运行过程中出现不可预料的问题,比如除以零、内存分配失败或文件无法打开时,可以抛出一个异常,由上层代码捕获并处理。
基本结构如下:
try {
// 可能出错的代码
throw std::runtime_error("出错了!");
} catch (const std::exception& e) {
std::cout << "捕获异常:" << e.what() << std::endl;
}这种写法在读取配置文件时特别有用。比如程序启动时加载用户设置,如果文件不存在,直接抛出异常比返回错误码更清晰。
常见异常类型使用不当
很多开发者习惯用int或bool作为函数返回值表示成功或失败,但在复杂逻辑中容易忽略检查。改用异常能让错误更显眼。
但要注意别滥用std::exception,应根据场景选择具体子类,比如:
- std::invalid_argument:参数不合法
- std::out_of_range:访问越界
- std::bad_alloc:内存申请失败
这样捕获时可以分情况处理,提升调试效率。
未捕获异常导致程序崩溃
如果抛出的异常没有被任何catch块捕获,程序会调用std::terminate终止。这种情况在线上服务中很致命,可能造成数据丢失。
可以在main函数外层包一层try-catch,兜底处理未知异常:
int main() {
try {
run_application();
} catch (const std::exception& e) {
std::cerr << "未处理异常:" << e.what() << std::endl;
return -1;
}
return 0;
}就像家里电路装保险丝一样,主路断了还能留条退路。
资源泄漏与RAII原则
异常发生时,普通清理代码可能被跳过。比如手动new对象后发生异常,delete就不会执行。
正确做法是利用RAII(资源获取即初始化),用智能指针或锁对象自动管理资源:
void process_data() {
std::unique_ptr<Resource> res(new Resource());
if (some_error) {
throw std::runtime_error("处理失败"); // 即使抛出异常,res也会自动释放
}
}这相当于出门不用惦记关灯,因为有感应开关帮你搞定。
嵌套异常与调试信息丢失
有时需要在捕获异常后包装再抛出,比如添加上下文信息。直接抛新异常会丢失原始调用链。
C++11支持嵌套异常:
try {
risky_operation();
} catch (...) {
std::throw_with_nested(std::runtime_error("在执行关键操作时失败"));
}之后可以用dynamic_cast捕获nested_exception,逐层提取原始异常,方便定位根本原因。
关闭异常机制带来的兼容问题
有些项目为了性能,在编译时用-fno-exceptions禁用C++异常。此时仍使用try/catch会导致编译错误。
可借助预处理器判断:
#ifdef __EXCEPTIONS
try {
may_throw();
} catch (...) {
handle_error();
}
#else
// 使用返回码替代
if (!may_throw_noexcept()) {
handle_error();
}
#endif这就像开车,有ABS系统就用刹车辅助,没有就得靠老司机经验控速。”}