%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
%%
# 异常处理机制
异常处理机制为程序**运行时**的 **异常检测** 和 **异常处理** 这两方面的协作提供支持,提供了一种将**错误处理代码**和**程序正常逻辑**进行**分离**的处理方式。
C++的异常处理包括**三个部分**:
- **`throw` 表达式** :异常检测部分通过该表达式来**抛出(raise)异常**。
- `try-catch` 异常处理
- 一个 **`try` 语句块**(try block):该语句块中抛出的异常将被捕获,并被某个 catch 子句处理。
- 一个或多个 **`catch` 子句**(catch clause):异常处理代码,处理异常
- 一套**异常类**(exception class):用于在 `throw` 表达式和相关的 `catch` 子句之间传递异常的具体信息
> [!info] "**异常安全**" 程序——**在异常发生期间==正确执行了 "清理" 工作==的程序**被称作为 "**异常安全**" 代码。
<br><br>
## 异常处理代码搜索过程
当异常被抛出时,被抛出的异常对象会被**传递到==调用栈==中逐层回退地寻找匹配的 `catch` 块进行处理** [^1] :
- 首先搜索**抛出该异常的函数**,若没有匹配的 `catch` 子句,则**终止该函数**并在**调用该函数的上层函数中**继续搜索;
- 以此类推,直到找到**匹配的 `catch` 子句**,则**把==程序的控制权==转移给==处理该异常的代码**==;
- 如果异常**传播到了 `main` 函数之外**仍未被捕获,程序**将调用 ==`std::terminate`==,默认会终止当前程序(非正常退出)**。
上述**搜寻 `catch` 时==依次退出函数==** 的过程称之为 "**==栈展开==**" (stack unwinding),
在此过程中,当退出某个块作用域或函数时,**编译器将负责确保其中的局部对象能够被正确销毁**。
<br><br>
## `throw` 表达式
`throw` 表达式用于抛出异常,关键字后跟着**要抛出的==异常对象==**,**表达式的类型**即为**抛出的==异常类型==**。
`throw` 表达式对 "**异常对象**" 进行 "**拷贝初始化**"。
```cpp
#include <iostream>
#include <stdexcept>
void MyFunc(bool error) {
if (error) {
throw std::runtime_error("An error occurred");
}
// 正常执行的代码
// ...
}
int main() {
try {
MyFunc(true);
} catch (std::runtime_error& e) {
std::cerr << "Caught an exception: " << e.what() << std::endl;
}
}
```
<br>
### 重新抛出
在 **`catch` 语句**或 **`catch` 语句直接或间接调用的函数**中,可使用 **==空的 `throw` 语句==**:`throw;`,
代表 "**==重新抛出==**"——**将其==捕获的异常对象==沿着调用链向上传递**。
> [!caution] `throw;` 重新抛出的是 "**==原始异常对象==**",故仅当 `catch` 参数是 "**引用类型**" 时,**`catch` 中对异常对象的修改**才会被保留
```cpp
try {
...
} catch (my_error& eObj) { // 引用类型
eObj.status = errCodes::severeErr; // 修改了异常对象, 重新抛出时会保留修改.
throw; // 重新抛出的是"原异常对象"
} catch (other_error eObj) { // 非引用, 而是得到原异常对象的副本
eObj.status = errCodes::badErr;
throw; // 这里重新抛出的是"原异常对象"
}
```
<br><br>
## `try-catch` 语句
`catch` 子句中的异常声明决定了 "**所能捕获的异常类型**",约束如下[^2]。
- `catch` 语句**按照==声明顺序==逐一进行匹配**。故越具有 "**专门性**" 的异常类应放在靠前位置,例如**派生类异常类型应当放在其基类之前**。
- `catch` 语句中声明的 "**异常类型**" 参数,可以是:
- **==值类型==**(将**拷贝得到异常对象的副本**)
- **==左值引用==**,不能是右值引用;
- **`...` 省略号**,表示 "**==捕获所有异常==**"。
- `catch` 语句中支持的**类型转换**只有三种,除此之外的**标准转换、用户自定义转换均不支持**。
- **非常量 => 常量**的转换;
- **派生类向基类或基类引用的转换**
- **数组名 => 指向数组元素类型的指针** & **函数名 => 函数指针**的转换;
```cpp
try {
// 程序正常执行逻辑
// ...
} catch (exception-declaration) { // 异常声明
// 异常处理逻辑...
} catch (exception-declaration) { // 异常声明
// 异常处理逻辑...
} catch (...) { // 异常声明中为省略号"..."时, 表示"捕获所有异常"
// 异常处理逻辑...
}
```
#### 捕获构造函数 "成员初始化列表" 中抛出的异常
在构造函数的 "**成员初始化列表的冒号之前**" 声明 `try` 关键字,并**在函数体后紧随 `catch` 子句**,称之为 "**==函数 try 语句块==**"。
这一声明将能同时捕获 "**==成员初始化列表中==**" 以及 "**==构造函数体内==**" 抛出的异常。
```cpp
template <typename T>
class Foo {
public:
// try 位于 "成员初始化列表的冒号之前".
// 将能捕获 "成员初始化列表" 以及 "构造函数体" 中抛出的异常.
Foo(std::initializer_list<T> li) try : data(std::make_shared<std::vector<T>>(li)) {
// ...
} catch (const std::bad_alloc& e) {
handle_out_of_memory(e);
}
private:
std::shared_ptr<std::vector<T>> data;
};
```
> [!NOTE] catch 子句中支持 "**多态**"。
>
> 当 `catch` 参数是一个**基类类型或其引用**时,可以用一个**派生类类型的异常对象**进行初始化。
> 若声明的异常类型为 **==基类引用==**,而实际捕获到 "**派生类实例**" 时,可以在 catch 子句中实现 "**多态**" 行为。
>
> ```cpp
> class Base {
> public:
> virtual void show() const {
> cout << "Base class exception" << endl;
> }
>
> virtual ~Base() = default;
> };
>
> class Derived : public Base {
> public:
> void show() const {
> cout << "Derived class exception" << endl;
> }
> };
>
> int main() {
> try {
> throw Derived(); // 抛出派生类对象
> } catch (Base& e) {
> e.show(); // 多态:: 调用派生类的虚函数版本.
> }
> }
> ```
>
>
<br><br><br>
# 标准异常
> ![[_attachment/02-开发笔记/01-cpp/异常处理相关/异常处理.assets/IMG-异常处理-0019FF59A0AC3A882494C6DD565C2E0B.png|700]]
> [!info] 上图中的标准异常类的定义在多个不同的头文件中:
>
> ![[_attachment/02-开发笔记/01-cpp/异常处理相关/异常处理.assets/IMG-异常处理-17CC25895E5CBE296491A30152AEBCBA.png|600]]
>
C++标准库提供了一系列**标准异常类**,定义在 `<stdexcept>` 等头文件中。
所有被 **C++语言本身**或**标准库**抛出的**异常类**,都派生自 **`std::exception` 基类**(定义于 `<exception` >头文件) 。
这些标准异常类可以划分成三组:
- **语言本身支持的异常**(Language support)
- 例如 `std::bad_alloc`(当`new`操作失败时抛出)
- **逻辑错误**(Logic error),`std::logic_error`
- 例如 `std::invalid_argument`
- **运行时错误**(Runtime error),`std::runtime_error`
- 例如 `std::overflow_error`
开发者也可以定义自己的异常类型,通常通过**继承`std::exception`类或其派生类**来实现,
确保自定义异常类型包含 `what()`成员函数,以返回异常描述。
> [!info]
> - "**运行时错误**" 是只有在**程序运行时才能检测到的错误**。
> - "**逻辑错误**" 是源自程序代码编写上的逻辑错误。
> [!info] 标准异常类的说明
>
> - 基类 `exception` 仅定义了拷贝构造函数、拷贝赋值运算符、虚析构函数、**==`.what()` 虚函数==**;
> - 类 `std::runtime_error` 与 `std::logic_error` 无默认构造函数,**只有一个可接受 C 风格字符串或 `std::string` 的构造函数**。
> - 类 `std::bad_cast`、`std::bad_alloc` 等**具有默认构造函数**。
>
>
> [!quote]
> C++标准库是异构的。其中不同的部分具有不同的设计和实现风格。**错误和异常处理**是体现差异的典型例子。
>
> - 库的某些部分,**如 string 类,支持详细的错误处理,检查可能发生的每一个问题,并在出现错误时抛出异常**。
> - 其他部分,如 STL 和值数组,更追求速度而不是安全性,因此它们**很少检查逻辑错误**,**只有在发生运行时错误时才抛出异常**。
<br>
## 异常类的信息
标准异常类都提供了 **`what()` 成员函数**,某些异常类还会提供 `code()` 成员函数。
- `what()` 返回一个**以 `\0` 结尾的字符数组**,其内容是 "**用于初始化异常对象**" 的信息。
- 该 C-string 在异常对象被摧毁后,或是当该异常对象被赋予新值后,就不再有效了
```cpp
namespace std {
class exception {
public:
virtual const char* what() const noexcept;
...
};
}
```
<br><br><br>
# 差错码差错状态
- **==差错码==**(Error Code):差错码是一种**轻型对象**,用来封装**差错码值**(error code value)。
- **差错码值**: 通常编译器实现指定 (implementation-specific),也有某些差错码值是标准化的。
- **==差错状态==**(Error Conditions):提供了 “**对错误描述的可移植抽象**” 的对象
对于异常,**C++标准库具体给出的有时是差错码,有时是差错状态**,取决于上下文和环境。
- Class `std::errc` 提供针对 `std::system_error` 异常而定义的**差错状态**。这些异常对应于`<cerrno>`或`<errno.h>` 中定义的标准系统错误号。
- Class `std::io_errc` 提供针对 `std::ios_base::failure` 异常而定义的**差错码**,这种异常由 `stream class` 抛出(since C++11);
- Class `std::future_errc` 提供针对 `std::future_error` 异常的**差错码**,这种异常由并发库(concurrency library) 抛出。
%%
## 处理差错码、差错状态
%%
<br><br>
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
# ♾️参考资料
# Footnotes
[^1]: 《C++ Primer》P175,P684
[^2]: 《C++ Primer》P175,P687