异常处理
翻译整理自 cppreference - 异常 与 std::exception。
1. try / catch / throw
#include <stdexcept>
double divide(int a, int b) {
if (b == 0) {
throw std::invalid_argument("divisor zero");
}
return double(a) / b;
}
try {
divide(1, 0);
} catch (const std::invalid_argument& e) {
std::cerr << "invalid: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cerr << "other: " << e.what() << "\n";
}2. 标准异常层级
<stdexcept> 提供:
std::exception(基类)std::logic_errorstd::invalid_argumentstd::domain_errorstd::length_errorstd::out_of_range
std::runtime_errorstd::overflow_errorstd::underflow_errorstd::range_error
<new> 中:std::bad_alloc
3. 自定义异常
class HttpError : public std::runtime_error {
public:
HttpError(int code, const std::string& msg)
: std::runtime_error(msg), code_(code) {}
int code() const { return code_; }
private:
int code_;
};
throw HttpError(500, "internal error");4. noexcept
声明函数不抛异常,编译器可优化:
void f() noexcept {
// 不能抛异常,否则 std::terminate
}
template <typename T>
void swap(T& a, T& b) noexcept(noexcept(a.swap(b))) { ... }C++17 起动态异常规格 throw(int) 已被移除,仅保留 noexcept。
5. RAII 与异常安全
构造函数抛异常时,析构不会调用(但已构造的成员会被析构)。RAII 是异常安全的核心:
class FileGuard {
public:
FileGuard(const char* path) : fp_(std::fopen(path, "r")) {
if (!fp_) throw std::runtime_error("open failed");
}
~FileGuard() { if (fp_) std::fclose(fp_); }
operator FILE*() const { return fp_; }
private:
FILE* fp_;
};
{
FileGuard f("a.txt");
// ...
} // 自动关闭,即使中间抛异常
6. 移动语义与异常
移动构造应 noexcept,否则 STL 会退化为拷贝(如 vector 扩容):
class V {
public:
V(V&&) noexcept = default;
};7. 异常的代价
- 抛异常时栈展开,开销较大
- 不抛异常时大多数实现零开销
- 不要用异常做控制流
- 不要在析构函数中抛异常
8. noexcept 函数中抛异常
void f() noexcept {
throw std::runtime_error("oops"); // 触发 std::terminate
}9. 重新抛出与异常链
try {
// ...
} catch (...) {
std::throw_with_nested(HttpError(500, "wrapped"));
}
// 解包 nested
try {
// ...
} catch (const std::exception& e) {
try { std::rethrow_if_nested(e); }
catch (const std::exception& nested) {
std::cerr << "caused by: " << nested.what();
}
}10. 异常 vs 错误码
| 场景 | 选择 |
|---|---|
| 罕见错误 | 异常 |
| 高频路径 | 错误码 |
| 不可恢复 | terminate / abort |
| 跨多层调用 | 异常 |
| 性能关键循环 | 错误码 |
C++23 引入 std::expected<T, E> 类似 Rust Result:
std::expected<int, std::string> parse(std::string_view s);
auto r = parse("42");
if (r) use(*r); else std::cerr << r.error();小结
异常是 C++ 表达错误的有力机制,配合 RAII 可写出强异常安全保证的代码。