第19.10章:De-fragmenting C++
在你写下下一个 throw 之前,先看这个
本章论证过:遗留异常是机制的问题,不是使用它的人的问题。这个论证并不新鲜:Herb Sutter 在 2019 年就讲过,而那场演讲正是本章所教一切的发源。演讲是 De-fragmenting C++: Making Exceptions and RTTI More Affordable and Usable,值得花那一小时,理由有两个:它从另一侧展示了同样的测量(二进制体积、一次抛出的代价),并给出了后来成为你从 第3.16章 起一直在用的 throws 通道的那套设计。
那场演讲
按演讲提出的顺序,值得留意的几点:
-
两种失败模型,以及它们为何无法共存。如果程序一半用返回值报告错误、一半用
throw,那么用返回值的那一半并不安全,因为它仍然会调用可能展开栈的代码。 -
体积数字。每个可能需要展开的函数都带着帧表;演讲统计了这在真实二进制里要花多少,而那正是 第19.7章 用
.eh_frame和.gcc_except_table量到的同一个机制。 - 延迟数字。一次抛出是一次栈遍历,所以它的代价取决于栈有多深、加载了多少共享对象 —— 第19.8章 对同一个错误量到大约一微秒对一纳秒。
-
两个字加一个标志位。演讲结尾的提案正是你一直在捕获的
::std::error:一个错误域指针、一个错误码,以及返回路径上的一个失败位,于是失败路径就是成功路径加一个分支。
看完之后
带着演讲留下的问题回到本章,因为这些页面会具体回答它们:
-
第19.1章 —— 错误域那张表:谁写它、五个钩子是什么、以及为什么
do_to_errc是必需的,好让每个错误都可以比较。 - 第19.2章 —— 提案留作"不可捕获"的那些情形怎么办:分配失败、尺寸溢出、不变量被破坏。
- 第19.6章 —— 与旧机制之间的边界,两个方向都有。
- 第19.9章 —— 容器的策略如何决定一次失败是被上报还是终止进程。
本书其余部分处处使用那场演讲提出的机制,无一例外。第20章 就是回报:它讲的正是这套机制为何重要,因为那一章讲的是在别的什么都行不通的目标上运行。