|
1 |
| -# 现代 C++ 中的错误处理 (未完工) |
| 1 | +# 现代 C++ 错误处理知多少 |
2 | 2 |
|
3 | 3 | [TOC]
|
4 | 4 |
|
5 |
| -## 为什么错误很重要? |
| 5 | +## 错误的分类 |
6 | 6 |
|
7 |
| -## 提前返回是好习惯! |
| 7 | +假设一个函数 `open` 的功能是打开文件。 |
| 8 | + |
| 9 | +```cpp |
| 10 | +int open(const char *path) { |
| 11 | + if (!file_exists(path)) { |
| 12 | + // 如果找不到文件怎么办? |
| 13 | + } |
| 14 | + // 成功找到文件: |
| 15 | + return get_handle(path); |
| 16 | +} |
| 17 | + |
| 18 | +int main() { |
| 19 | + int file = open("file.txt"); |
| 20 | + |
| 21 | + char buf[64]; |
| 22 | + read(file, buf, sizeof buf); |
| 23 | + ... |
| 24 | +} |
| 25 | +``` |
| 26 | +
|
| 27 | +理想情况下,所有的函数都能成功执行,都能正常返回结果…… |
| 28 | +
|
| 29 | +可现实中,我们不能假设一个程序,永远正确执行(例如文件可能被用户误删除,或者内存不够用等)。 |
| 30 | +
|
| 31 | +更有甚者,有时错误是计划的一部分(例如文件不存在,则创建一个新文件,而不是将其视为不可修复的错误)。 |
| 32 | +
|
| 33 | +特别是涉及 IO 操作的任务,出现一些细小错误的情况是很多的。要区分哪些是可以修复的错误,哪些是不可挽回的错误。 |
| 34 | +
|
| 35 | +例如当网络连接失败时,我们可以重新尝试连接两三次,如果还是不行,那才认为是真的失败了。 |
| 36 | +
|
| 37 | +因此,我们把错误分为两大类: |
| 38 | +
|
| 39 | +- 可恢复错误:不是特别严重的,甚至是计划之中的,经常发生的错误。可以通过一定操作来弥补这类错误,或将其转化为其他不同类型的错误。 |
| 40 | +- 不可恢复错误:非常严重的错误,或者是发生概率很低平时没必要特殊处理的错误。一旦发生,整个程序都无法继续执行下去,必须全身而退,整个进程或线程都将终止。 |
| 41 | +
|
| 42 | +### 不可恢复错误 |
| 43 | +
|
| 44 | +不可恢复错误的处理最简单,我们只需要在被调用者检测到错误的分支中,直接调用 `exit` 函数“终止程序”即可。 |
| 45 | +
|
| 46 | +```cpp |
| 47 | +int open(const char *path) { |
| 48 | + if (!file_exists(path)) { |
| 49 | + // 找不到文件我就自杀! |
| 50 | + exit(1); |
| 51 | + // 程序不会执行到此 |
| 52 | + } |
| 53 | + return get_handle(path); |
| 54 | +} |
| 55 | +
|
| 56 | +int main() { |
| 57 | + int file = open("file.txt"); |
8 | 58 |
|
9 |
| -TODO |
| 59 | + char buf[64]; |
| 60 | + read(file, buf, sizeof buf); |
| 61 | + ... |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +- 缺点:`exit` 会直接退出整个进程!没有任何给调用者挽回的机会,因此只能用于“不可恢复错误”这个类型。 |
| 66 | +- 优点:调用者无需做任何判断处理,写起来就好像被调用函数“总是成功”一样,总能返回结果。因为如果被调用者失败的话,他会调用 `exit` 自杀,就不会返回到调用者中了。 |
| 67 | + |
| 68 | +> {{ icon.fun }} 小时候看这集变成“码码的萤火虫”了。 |
| 69 | +
|
| 70 | +### 可恢复错误 |
| 71 | + |
| 72 | +有时候,我们对于部分错误,是有挽回机会的,不希望因为一点可以修复的小错误就把整个程序终止掉。 |
| 73 | + |
| 74 | +要不要挽回应该由调用者的具体业务决定,而封装良好的 API(`open`)应该忠实地把错误报告给调用者(`main`)。 |
| 75 | + |
| 76 | +让调用者来决定要杀了还是抢救,而不是自作主张地直接自杀。 |
| 77 | + |
| 78 | +```cpp |
| 79 | +int open(const char *path) { |
| 80 | + if (!file_exists(path)) { |
| 81 | + // 找不到文件,就返回 -1 这个“出错特殊值”代替 |
| 82 | + return -1; |
| 83 | + } |
| 84 | + return get_handle(path); |
| 85 | +} |
| 86 | + |
| 87 | +int main() { |
| 88 | + int file = open("file.txt"); |
| 89 | + if (file == -1) { // 缺点是 main 里面必须判断返回值是否为“出错特殊值” |
| 90 | + // 如果找不到文件,尝试进行处理 |
| 91 | + create_empty_file("file.txt"); |
| 92 | + // 重新尝试打开 |
| 93 | + file = open("file.txt"); |
| 94 | + if (file == -1) { // 如果还是出错,那就没救了 |
| 95 | + exit(-1); // 直接自杀 |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + char buf[64]; |
| 100 | + read(file, buf, sizeof buf); |
| 101 | + ... |
| 102 | +} |
| 103 | +``` |
| 104 | +
|
| 105 | +### 我该如何抉择 |
| 106 | +
|
| 107 | +## 调用者与被调用者 |
| 108 | +
|
| 109 | +`main` 是调用者,`open` 是被调用者。 |
| 110 | +
|
| 111 | +被调用者函数可能产生错误,也可能正常执行。 |
| 112 | +
|
| 113 | +## 提前返回是好习惯! |
10 | 114 |
|
11 | 115 | ## 异常
|
12 | 116 |
|
|
0 commit comments