1. 抛出异常:throw
表达式
当函数或代码模块发现自身无法继续正常工作、但又无法在本地修复时,就用
throw expr;
来“抛出”一个异常。这里的 expr
可以是:
- 标准库异常(如
std::runtime_error("…")
) - 自定义的异常类对象
- 任意可以复制的对象
if (isbn1 != isbn2) {
// 无法在此处合并记录,抛出运行时错误
throw std::runtime_error("ISBN must match to combine Sales_data");
}
抛出后,当前函数立即中断,控制流沿调用链向上回溯,寻找匹配的 catch
。
2. 捕获与处理:try
/ catch
在可能抛出异常的代码周围,用一个 try
块 包裹,并紧跟一个或多个 catch
子句:
try {
// 可能会 throw
auto result = computeBigThing(data);
std::cout << result << "\n";
}
catch (const std::runtime_error &e) {
// 针对 runtime_error 的专门处理
std::cerr << "运行时错误: " << e.what() << "\n";
}
catch (const std::exception &e) {
// 兜底:处理所有 std::exception 派生的异常
std::cerr << "其他异常: " << e.what() << "\n";
}
- 抛出后,第一个匹配抛出类型(或其基类)的
catch
会被执行。 - 执行完 后,控制流跳到所有
catch
块之后继续,期间栈上被跳过函数的局部对象会正常析构。
3. 标准异常类概览
C++ 标准库在头文件 <stdexcept>
、<exception>
等中定义了一整套常用异常类型:
异常类型 | 语义 |
---|---|
std::exception | 最基类,仅报告“有异常” |
逻辑错误 | <stdexcept> |
├─ std::logic_error | 程序逻辑错误,不是运行时问题(如非法参数) |
├─ std::domain_error | 参数值不在函数定义域内 |
├─ std::invalid_argument | 参数格式不合法 |
├─ std::length_error | 容器或字符串过长 |
└─ std::out_of_range | 下标、迭代器超出有效范围 |
运行时错误 | <stdexcept> |
├─ std::runtime_error | 通用运行时错误 |
├─ std::overflow_error | 算术运算上溢 |
├─ std::underflow_error | 算术运算下溢 |
└─ std::range_error | 结果超出允许的数值范围 |
此外,动态内存分配相关的 <new>
中有 std::bad_alloc
;类型转换的 <typeinfo>
中有 std::bad_cast
。
4. 异常安全最佳实践
-
只抛出异常,不在库层直接
exit()
保持错误处理交给调用者。 -
捕获最具体的类型,再捕获基类
catch(const std::invalid_argument &e) { … } catch(const std::exception &e) { … } // 兜底
-
保持异常的基本保证
- 不抛异常:对外接口尽量不丢失异常。
- 强烈异常保证:若抛出异常,当前操作无副作用(事务语义)。
-
避免使用裸指针管理资源
结合 RAII(资源获取即初始化)模式:std::unique_ptr
、std::vector
、std::string
等,确保栈展开时自动清理。 -
不要捕获所有异常并吞掉
catch(...)
应仅用于日志记录或清理,然后重新抛出,否则程序状态可能不明。
5. 小结
throw
:抛出运行时检测不到、无法处理的“问题”。try
/catch
:捕获并专门处理特定类型的错误。- 标准异常类:从
std::exception
延伸出丰富的逻辑与运行时错误类型。 - 异常安全性:通过 RAII、恰当的捕获层次和保证级别,提升程序稳健性。
掌握这一机制后,您就能用简洁优雅的方式,把散落在代码各处的“错误检查”逻辑收拢到少数几个 catch
块中,大大提升可读性和可维护性。