《Exceptional C++》— Herb Sutter

源自 Guru of the Week 专栏的 47 个 C++ 工程难题与解答,异常安全与现代 C++ 设计的经典之作。

基本信息

  • 原名:Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions
  • 作者:Herb Sutter(C++ 标准委员会核心成员)
  • 中文版:侯捷 等合译(draft 版,民间译本)
  • 出版社:Addison-Wesley(英文版 2000 年)
  • 来源:Usenet 专栏 Guru of the Week(GotW)第 1~30 期及扩充内容
  • 页数:约 80 页(draft 版节译)/ 全书约 300 页
  • 难度定位:进阶-专家级,面向已熟练掌握 C++ 的程序员

核心定位

这本书不是入门教材,也不是语法参考书——它是一本软件工程实战训练手册。采用「问题/解答」格式,每个条款都以一个 C++ 编程难题开场,引导读者思考,再给出深度剖析。

Scott Meyers 评价:「这恐怕是第一本写给已经完全懂 C++ 的人看的书。」

与 《Effective C++》-第三版-55个改善程序与设计的具体做法-Scott-Meyers 相比:

  • Effective C++ 是「条款式」的规则清单,体系化地告诉你「应该怎么做」
  • Exceptional C++ 是「谜题式」的案例分析,通过解题让你自己发现「为什么应该这么做」
  • 两本书互为补充,前者是地图,后者是迷宫探险

全书结构:11 章 47 条款

第 4 章:泛型编程与 C++ 标准库(条款 1-7)

核心问题:如何正确、高效地使用 STL 迭代器、算法、容器?

条款GotW #主题难度
1#18Iterators 迭代器7
2-3#29不区分大小写的字符串-
4-5#16具有最大可复用性的通用容器-
6#2临时对象-
7#3使用标准库-

关键要点:

  • 迭代器失效是 STL 编程中最常见的陷阱之一:插入/删除元素后迭代器可能失效
  • 永远优先使用标准库算法,而不是手写循环——更快、更安全、更少 bug
  • istream_iterator / ostream_iterator 是输入输出与算法的桥梁
  • 不区分大小写的字符串比较:不要用 strcasecmp(C 风格、非标准),而应通过 char_traits 自定义或提供比较谓词
  • 临时对象优化:理解 RVO(返回值优化) 和 拷贝消除,避免不必要的拷贝

第 5 章:异常安全的主题与相关技术(条款 8-19)

这是本书的核心章节,也是书名「Exceptional C++」的由来。

条款GotW #主题难度
8-17#8挑战篇——异常处理的安全性9
18#20代码的复杂性(一)9
19#21代码的复杂性(二)-

异常安全的三个层次(从弱到强):

  1. 基本保证:操作失败后,对象仍处于一致状态(无资源泄漏、不变式成立)
  2. 强保证:操作要么完全成功,要么不产生任何效果(提交/回滚语义)
  3. 不抛出保证(nothrow):操作绝不会抛出异常

核心原则与技法:

  • Copy-and-Swap 惯用法:先创建副本(可能抛异常),再用不抛异常的 swap 提交——实现强保证的经典手法
  • Pimpl 惯用法:将实现细节隐藏在指针后,不仅降低编译依赖,也有助于异常安全
  • 资源管理即对象管理:使用 RAII 类(auto_ptr、智能指针、scoped_ptr 等)自动管理资源
  • 析构函数绝不抛出异常:否则会导致 std::terminate 调用,程序直接终止

条款 18 经典案例:仅 4 行代码的函数 EvaluateSalaryAndReturnName,实际有 23 条执行路径——其中 3 条正常路径 + 20 条异常路径。这揭示了 C++ 异常带来的隐性复杂度。

第 6 章:类的设计与继承(条款 20-25)

条款GotW #主题
20#4Class 技术(构造/析构/赋值运算符)
21#5改写虚拟函数(Overriding Virtual Functions)
22-2314-15Classes 之间的关系
24-使用/滥用继承
25#13面向对象程序设计

关键要点:

  • 三大件:拷贝构造、拷贝赋值、析构——只要自己写了其中一个,通常三个都要写
  • 虚函数重写规则:函数签名必须完全匹配(包括 const 性),否则不是重写而是隐藏
  • is-a 原则:公有继承必须严格满足「派生类 IS-A 基类」的关系
  • 继承是 C++ 中最强的耦合关系,慎用。优先考虑组合/委托
  • LSP(Liskov 替换原则):派生类必须能在任何基类出现的地方替换之

第 7 章:编译级防火墙及 Pimpl Idiom(条款 26-30)

条款GotW #主题
26-28#7编译期的依赖性
29#24编译级防火墙
30#28”Fast Pimpl” 技术

Pimpl(Pointer to Implementation)惯用法:

  • 将类的私有成员封装到一个前置声明的实现类中,通过指针访问
  • 核心收益:
    1. 降低编译依赖:修改实现不影响头文件,使用者无需重新编译
    2. 二进制兼容性:对象大小和布局不变
    3. 隐藏实现细节:头文件只暴露接口
  • Fast Pimpl:使用自定义分配器(如固定大小内存池)避免每次 new/delete 的开销
  • 与 《Effective STL》-50条有效使用STL的经验-Scott-Meyers 中关于智能指针的条款互相印证

第 8 章:名称搜索、命名空间、接口原则(条款 31-34)

条款GotW #主题
31#30名称搜索(Name Lookup)
32-34-接口原则(The Interface Principle)

关键概念:

  • ADL(Argument-Dependent Lookup,参数依赖查找):也叫 Koenig Lookup——调用函数时,如果参数的命名空间里有同名函数,也会被纳入候选集
  • 接口原则:一个类的接口不仅包括它的成员函数,也包括与它在同一命名空间的非成员函数(因为 ADL 会找到它们)
  • 这就是为什么 std::cout << x; 不需要写 std::operator<<——ADL 自动找到了 std 命名空间里的 operator<<

第 9 章:内存管理(条款 35-37)

条款GotW #主题
35-369-10内存管理(一)(二)
37#25auto_ptr

关键要点:

  • operator new / operator delete 可以被类重载
  • Placement new:在已分配内存上构造对象
  • auto_ptr 的转移所有权语义(C++98 时代的智能指针,C++11 后被 unique_ptr 取代)
  • 内存管理的黄金法则:谁分配谁释放,用 RAII 自动化

第 10 章:陷阱、易犯错误和反常作法(条款 38-41)

条款GotW #主题
38#11对象等同问题(Object Identity)
39#19自动转换(Automatic Conversions)
40-4122-23对象的生存期(Object Lifetimes)

常见陷阱:

  • 不要写 if( &x == &y ) 来判断对象等同——这比较的是地址,不是值
  • 隐式类型转换是 bug 的温床:单参数构造函数加 explicit,转换运算符要谨慎
  • 临时对象的生存期:绑定到 const 引用的临时对象生存期会延长到引用的作用域结束
  • 悬垂指针/引用:永远不要返回局部对象的指针或引用

第 11 章:杂项主题(条款 42-47)

条款GotW #主题
42#1变量的初始化
43#6正确使用 const(const-correctness)
44#17类型转换(Casts)
45#26bool
46#27转呼叫函数(Forwarding Functions)
47#12控制流(Control Flow)

核心要点:

  • const 正确性是 C++ 独有的强大特性:能用 const 就用 const——成员函数、参数、返回值
  • 优先使用 C++ 风格的类型转换(static_cast / dynamic_cast / const_cast / reinterpret_cast),不要用 C 风格强制转换
  • dynamic_cast 是运行时类型识别,有性能开销,且往往暗示设计问题
  • 转发函数:用于完美转发参数到实际执行函数,减少代码重复

与其他 C++ 经典的关联

可行动点

  1. 审视你的代码:找一个函数,数一下它有多少条异常路径——你可能会惊讶
  2. 检查异常安全等级:你的类提供的是基本保证、强保证还是 nothrow 保证?
  3. 运用 Pimpl:对于稳定度高的模块,考虑用 Pimpl 降低编译依赖
  4. const 正确性审查:给能加 const 的成员函数和参数都加上 const
  5. 优先使用标准库算法:替换手写循环为 std::for_each / std::find / std::transform 等
  6. 警惕隐式转换:单参数构造函数加 explicit,减少意外的类型转换

版本说明

本笔记基于 7z 压缩包中的「Exceptional C++中文版(draft).pdf」——这是一个民间合译的草稿版本(约 80 页),非官方完整版。官方中文版由侯捷翻译,内容更完整、翻译质量更高。

英文版原书共约 300 页,包含更完整的代码示例和深入讨论。