C++ 标准库 exception

下面对 C++ 标准库中 <exception> 头文件提供的异常处理基础设施做一次系统、深入的梳理,包括核心类型、标准派生异常、异常传播控制函数、嵌套异常支持,以及实践建议。


一、概述

  • <exception> 定义了 C++ 异常处理机制的基础类型 std::exception 及其若干派生类,同时提供了控制异常传播和终止的函数。
  • 所有类型和函数均在 std 命名空间中,可与 try/catchthrow、以及 <stdexcept> 中的更多具体异常类型配合使用。

二、核心类型

1. std::exception

class exception {
public:
    exception() noexcept;
    exception(const exception&) noexcept;
    exception& operator=(const exception&) noexcept;
    virtual ~exception();

    virtual const char* what() const noexcept;
};
  • 说明:所有标准异常类都直接或间接派生自 std::exception,它提供一个可重写的 what() 接口,用于描述异常原因。
  • 行为:默认 what() 返回实现定义的空或通用信息;派生类通常返回更具体的错误消息。

2. 常见直接派生

异常类型说明what() 示例
std::bad_exception由 std::unexpected 抛出"bad exception"
std::bad_alloc内存分配失败"std::bad_alloc"
std::bad_castdynamic_cast 对引用失败抛出"std::bad_cast"
std::bad_typeid对空指针应用 typeid 抛出"bad typeid"
std::nested_exception用于嵌套异常(throw_with_nested与绑定的内部异常信息配合

其它更具体的标准异常,如 std::overflow_errorstd::out_of_rangestd::invalid_argument等在 <stdexcept> 中定义,也是继承自 std::exception


三、控制异常传播与程序终止

1. std::terminate 与 std::set_terminate

[[noreturn]] void terminate() noexcept;
using terminate_handler = void(*)();
terminate_handler set_terminate(terminate_handler f) noexcept;
terminate_handler get_terminate() noexcept;
  • std::terminate():在以下情况自动调用:
    • 异常在析构时传播出析构函数(栈展开期间抛出新异常)
    • 没有匹配的 catch
    • 调用 std::unexpected(已弃用)
  • 自定义处理:通过 set_terminate 注册一个回调,在 terminate 被触发时执行,如记录日志、清理资源,再调用默认终止或 std::abort()

2. std::unexpected 与 std::set_unexpected  (已弃用 C++17)

  • 旧机制:当函数的 throw() 异常规范 (dynamic‑exception specifications) 被违反时调用,现代 C++ 已弃用,建议不再使用。

四、异常再抛与嵌套异常

1. 异常再抛

try {
    // …
} catch (…) {
    // 做些清理
    throw;            // 再抛当前捕获异常
}
  • throw; 可保留原始异常类型和调用栈信息,用于在清理后将异常继续向上传播。

2. 嵌套异常

#include <exception>
class MyError : public std::exception, public std::nested_exception {
    const char* what() const noexcept override { return "MyError"; }
};

void f() {
    try {
        // 可能抛出 std::runtime_error
        g();
    } catch (...) {
        std::throw_with_nested(MyError{});  // 将当前异常与 MyError 绑定
    }
}

int main() {
    try { f(); }
    catch (const std::exception& e) {
        std::cout << e.what() << "\n";
        try { std::rethrow_if_nested(e); }
        catch (const std::exception& inner) {
            std::cout << "  nested: " << inner.what() << "\n";
        }
    }
}
  • std::nested_exception:基类,提供 throw_with_nested 和 rethrow_if_nested 支持,将一个异常与捕获的内部异常链起来。

五、与标准库其他组件的配合

  • <stdexcept> 中定义了大量常用具体异常类型,如
    • 算术错误:std::overflow_errorstd::underflow_error
    • 范围与逻辑错误:std::out_of_rangestd::logic_errorstd::invalid_argument
  • <typeinfo> 与 <exception> 联动:std::bad_typeid 和 std::bad_cast 都继承自 std::exception,可在 catch(const std::exception&) 中统一处理。

六、实践建议

  1. 统一基类捕获
    • 在程序入口或线程边界处,使用try { /* ... */ } catch (const std::exception& e) { /* 处理或日志 */ } catch (...) { /* 兜底 */ } 既能捕获所有标准异常,也能避免未知异常导致程序崩溃。
  2. 避免异常安全违规
    • 在析构函数中不要抛出异常;若必须,则应捕获并调用 std::terminate()
  3. 自定义 terminate_handler
    • 在关键系统中,用 set_terminate 拦截意外终止点,记录调用栈、转储核心等。
  4. 恰当使用嵌套异常
    • 在封装底层异常时,用 throw_with_nested 增加上下文;消费端用 rethrow_if_nested 展开。
  5. 弃用异常规范
    • 动态异常规范 (throw()throw(Type)) 在 C++17 被移除,应改用 noexcept;并在可能抛出时标注函数 noexcept(false)(默认)或 noexcept(true)
  6. 性能考虑
    • C++ 异常机制在抛出时成本较高,但在正常路径(未抛出)开销接近零;避免将异常用于普通控制流。

通过以上对 <exception> 中异常基类、标准派生异常、终止与意外处理、嵌套异常支持及实践建议的全面梳理,相信能帮助你在设计健壮、异常安全的 C++ 应用时,正确、高效地使用标准库提供的异常基础设施。祝编码顺利!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注