异常处理

翻译整理自 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_error
      • std::invalid_argument
      • std::domain_error
      • std::length_error
      • std::out_of_range
    • std::runtime_error
      • std::overflow_error
      • std::underflow_error
      • std::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 可写出强异常安全保证的代码。