C++ 标准库 exception
下面对 C++ 标准库中 <exception>
头文件提供的异常处理基础设施做一次系统、深入的梳理,包括核心类型、标准派生异常、异常传播控制函数、嵌套异常支持,以及实践建议。
一、概述
<exception>
定义了 C++ 异常处理机制的基础类型std::exception
及其若干派生类,同时提供了控制异常传播和终止的函数。- 所有类型和函数均在
std
命名空间中,可与try
/catch
、throw
、以及<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_cast | dynamic_cast 对引用失败抛出 | "std::bad_cast" |
std::bad_typeid | 对空指针应用 typeid 抛出 | "bad typeid" |
std::nested_exception | 用于嵌套异常(throw_with_nested ) | 与绑定的内部异常信息配合 |
其它更具体的标准异常,如
std::overflow_error
、std::out_of_range
、std::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_error
、std::underflow_error
- 范围与逻辑错误:
std::out_of_range
、std::logic_error
、std::invalid_argument
- 算术错误:
<typeinfo>
与<exception>
联动:std::bad_typeid
和std::bad_cast
都继承自std::exception
,可在catch(const std::exception&)
中统一处理。
六、实践建议
- 统一基类捕获
- 在程序入口或线程边界处,使用
try { /* ... */ } catch (const std::exception& e) { /* 处理或日志 */ } catch (...) { /* 兜底 */ }
既能捕获所有标准异常,也能避免未知异常导致程序崩溃。
- 在程序入口或线程边界处,使用
- 避免异常安全违规
- 在析构函数中不要抛出异常;若必须,则应捕获并调用
std::terminate()
。
- 在析构函数中不要抛出异常;若必须,则应捕获并调用
- 自定义
terminate_handler
- 在关键系统中,用
set_terminate
拦截意外终止点,记录调用栈、转储核心等。
- 在关键系统中,用
- 恰当使用嵌套异常
- 在封装底层异常时,用
throw_with_nested
增加上下文;消费端用rethrow_if_nested
展开。
- 在封装底层异常时,用
- 弃用异常规范
- 动态异常规范 (
throw()
,throw(Type)
) 在 C++17 被移除,应改用noexcept
;并在可能抛出时标注函数noexcept(false)
(默认)或noexcept(true)
。
- 动态异常规范 (
- 性能考虑
- C++ 异常机制在抛出时成本较高,但在正常路径(未抛出)开销接近零;避免将异常用于普通控制流。
通过以上对 <exception>
中异常基类、标准派生异常、终止与意外处理、嵌套异常支持及实践建议的全面梳理,相信能帮助你在设计健壮、异常安全的 C++ 应用时,正确、高效地使用标准库提供的异常基础设施。祝编码顺利!