C++ Coding Standards — 101条编码规范与最佳实践

两位 C++ 大师的集大成之作:Herb Sutter(ISO C++ 标准委员会主席)与 Andrei Alexandrescu(Modern C++ Design 作者,Loki 库设计者)联手打造,C++ In-Depth Series 出品,Bjarne Stroustrup 亲任丛书编辑。

核心定位

这不是一本”教你 C++ 语法”的书,而是一本**“教你如何写出好的 C++ 代码”的书**。

  • 101 条规则,每条都有 Summary / Discussion / Examples / Exceptions / References 结构
  • 规则不是教条:每条都说明”为什么”以及”何时可以不遵守”
  • 覆盖从组织级政策到具体语法细节的完整谱系
  • 基于社区最佳实践,而非个人偏好

书中名言:“好的编码标准应该反映社区经过验证的最佳经验,而不是某个人的个人癖好。“


全书结构(12大部分 + 101条规则)

第 0 条:元规则

  • Ch 0. Don’t sweat the small stuff.(不要为小事操心) — 知道什么不需要标准化。风格之争(花括号位置、命名约定)不值得强制;真正重要的是设计、正确性、可维护性。

第一部分:组织与政策问题(Ch 1-4)

编号规则核心要点
1Compile cleanly at high warning levels最高警告级别下零警告通过;将警告视为错误
2Use an automated build system一键构建;一次构建所有配置
3Use a version control system版本控制是基本纪律
4Invest in code reviews代码审查是质量投资;审查代码而非人

第二部分:设计风格(Ch 5-13)

编号规则核心要点
5Give one entity one cohesive responsibility单一职责原则(SRP);一个实体只做一件事
6Correctness, simplicity, and clarity come first正确性、简洁性、清晰性优先于性能和技巧
7Know when and how to code for scalability为可扩展性编码要有意识、有依据地进行
8Don’t optimize prematurely不要过早优化;先让代码正确,再让它快
9Don’t pessimize prematurely不要过早悲观化;不要无缘无故地写低效代码
10Minimize global and shared data最小化全局和共享数据;减少耦合和竞态条件
11Hide information信息隐藏;封装实现细节
12Know when and how to code for concurrency并发编码要有意识;了解内存模型和锁
13Ensure resources are owned by objects. Use RAII and smart pointersRAII 是资源管理基石;智能指针是首选

关键洞察:第 8 条(不提早优化)和第 9 条(不过早悲观)是一对——前者说”不要为了不确定的性能牺牲清晰度”,后者说”不要在没有成本的情况下写低效代码”(如 ++i 优于 i++ 对迭代器而言)。

第三部分:编码风格(Ch 14-24)

编号规则核心要点
14Prefer compile- and link-time errors to run-time errors能在编译期发现的错误不要留到运行期(static_assert、类型系统)
15Use const proactively主动使用 const;const 是文档、是约束、是优化机会
16Avoid macros尽量避免宏;宏不受作用域约束、不遵循类型系统
17Avoid magic numbers避免魔法数字;用命名常量替代
18Declare variables as locally as possible变量尽可能局部声明;缩小作用域
19Always initialize variables始终初始化变量;未初始化是 bug 来源
20Avoid long functions. Avoid deep nesting避免长函数和深嵌套;函数应一屏可读
21Avoid initialization dependencies across compilation units避免跨编译单元的初始化顺序依赖(静态初始化顺序惨败)
22Minimize definitional dependencies. Avoid cyclic dependencies最小化定义依赖;避免循环依赖
23Make header files self-sufficient头文件应自给自足;包含它所需的所有头文件
24Always write internal include guards. Never write external include guards始终写内部 include guard(#ifndef),不要依赖外部 guard

第四部分:函数与操作符(Ch 25-31)

编号规则核心要点
25Take parameters appropriately by value, (smart) pointer, or reference参数传递方式选择:值/指针/引用——有明确的选择准则
26Preserve natural semantics for overloaded operators重载操作符要保持自然语义(如 operator+ 不应修改操作数)
27Prefer the canonical forms of arithmetic and assignment operators算术和赋值操作符的规范形式(a+=b 优于 a=a+b,operator+ 用 += 实现)
28Prefer the canonical form of ++ and —. Prefer calling the prefix forms++/— 的规范形式;优先使用前缀版本
29Consider overloading to avoid implicit type conversions考虑重载以避免隐式类型转换
30Avoid overloading &&,
31Don’t write code that depends on the order of evaluation of function arguments不要依赖函数参数求值顺序(未定义行为)

参数传递决策树(Ch 25 核心):

  • 输出/入出参数:用引用(或指针,表示可选)
  • 输入参数且 cheap to copy:按值传递
  • 输入参数且 expensive to copy:按 const 引用传递
  • 输入参数需要拷贝一份:按值传递 + move(C++11 风格)

第五部分:类设计与继承(Ch 32-46)

编号规则核心要点
32Be clear what kind of class you’re writing明确你在写什么类型的类(值类型、多态基类、混入、工具类…)
33Prefer minimal classes to monolithic classes偏好小类而非巨型类;单一职责
34Prefer composition to inheritance组合优于继承
35Avoid inheriting from classes not designed to be base classes不要继承非为基类设计的类(如标准库容器)
36Prefer providing abstract interfaces优先提供抽象接口(纯抽象基类)
37Public inheritance is substitutability公有继承 = is-a = 可替换性(LSP)
38Practice safe overriding安全地重写虚函数;显式使用 virtual(C++11 用 override)
39Consider making virtual functions nonpublic, and public functions nonvirtualNVI 惯用法:虚函数设为 private/protected,公有接口非虚
40Avoid providing implicit conversions避免隐式转换;用 explicit
41Make data members private, except in behaviorless aggregates数据成员设为 private;只有 C 风格结构体(无行为的聚合)例外
42Don’t give away your internals不要泄露内部实现;不要返回非 const 指针/引用到内部成员
43Pimpl judiciously合理使用 Pimpl 惯用法(编译防火墙)
44Prefer writing nonmember nonfriend functions优先写非成员非友元函数(增强封装性)— Meyers 经典论断
45Always provide new and delete togethernew 和 delete 要成对提供
46If you provide any class-specific new, provide all standard forms如果提供了类专属 new,就要提供所有标准形式

NVI 模式(Ch 39):公有接口非虚,虚函数设为私有。基类控制前置/后置操作(不变量检查、日志、锁),派生类只实现核心逻辑。这是 Template Method 模式在 C++ 中的惯用法。

封装性判断(Ch 44):一个函数需要访问类的私有成员越少,封装性越好。非成员非友元函数 = 最大封装性。

第六部分:构造、析构与赋值(Ch 47-56)

编号规则核心要点
47Define and initialize member variables in the same order成员初始化顺序与声明顺序一致(否则实际顺序与你预期不同)
48Prefer initialization to assignment in constructors构造函数中优先使用初始化列表,而非赋值
49Avoid calling virtual functions in constructors and destructors构造/析构期间不要调用虚函数(调用的是当前层级的版本,不是最终派生类的)
50Make base class destructors public and virtual, or protected and nonvirtual基类析构函数:要么 public + virtual(多态删除),要么 protected + nonvirtual(禁止通过基类指针删除)
51Destructors, deallocation, and swap never fail析构函数、释放操作、swap 绝不允许失败(不抛异常)
52Copy and destroy consistently拷贝和销毁要一致;Rule of Zero / Rule of Three / Rule of Five
53Explicitly enable or disable copying显式启用或禁用拷贝(=default / =delete)
54Avoid slicing. Consider Clone instead of copying in base classes避免对象切割;基类拷贝用 Clone(虚拷贝构造)
55Prefer the canonical form of assignment赋值操作符的规范形式(自赋值安全、强异常安全)
56Provide a no-fail swap提供不抛出异常的 swap(且正确实现:成员级 swap + ADL)

Rule of Zero/Three/Five(Ch 52 延伸):

  • 零法则:如果可以,让编译器自动生成所有特殊成员函数(用 RAII 成员)
  • 三法则:如果需要自定义析构函数/拷贝构造/拷贝赋值中的一个,通常需要全部三个
  • 五法则(C++11):三法则 + 移动构造 + 移动赋值

第七部分:命名空间与模块(Ch 57-63)

编号规则核心要点
57Keep a type and its nonmember function interface in the same namespace类型及其非成员函数接口放在同一命名空间(ADL 能找到它们)
58Keep types and functions in separate namespaces unless they’re intended to work together不相关的类型和函数放在不同命名空间(避免意外的 ADL)
59Don’t write namespace usings in a header file or before an include不要在头文件中或 include 前写 using namespace(污染全局)
60Avoid allocating and deallocating memory in different modules避免在不同模块中分配和释放内存(不同堆/不同 CRT)
61Don’t define entities with linkage in a header file不要在头文件中定义有链接的实体(会导致 ODR 违反)
62Don’t allow exceptions to propagate across module boundaries不要让异常跨模块边界传播(不同编译器/不同运行时)
63Use sufficiently portable types in a module’s interface模块接口中使用足够可移植的类型

第八部分:模板与泛型(Ch 64-67)

编号规则核心要点
64Blend static and dynamic polymorphism judiciously明智地混合静态多态(模板)和动态多态(虚函数)
65Customize intentionally and explicitly有意且显式地定制(特化、重载要有明确意图)
66Don’t specialize function templates不要特化函数模板;用重载替代(函数模板特化不参与重载决议)
67Don’t write unintentionally nongeneric code不要无意中写出非泛型代码;模板应真正泛化

Ch 66 是 Alexandrescu 的经典洞见:函数模板特化不参与重载决议,容易导致意外行为。优先使用函数重载或类模板特化。

第九部分:错误处理与异常(Ch 68-75)

编号规则核心要点
68Assert liberally to document internal assumptions and invariants大量使用 assert 记录内部假设和不变量
69Establish a rational error handling policy, and follow it strictly建立合理的错误处理策略并严格遵守
70Distinguish between errors and non-errors区分错误和非错误(预期内的情况不是错误)
71Design and write error-safe code设计并编写错误安全的代码(异常安全等级:基本/强/不抛)
72Prefer to use exceptions to report errors优先用异常报告错误(C++ 的标准错误机制)
73Throw by value, catch by reference按值抛出,按引用捕获
74Report, handle, and translate errors appropriately适当报告、处理、转换错误
75Avoid exception specifications避免异常规格(C++11 已弃用动态异常规格)

异常安全三级保证(Ch 71):

  1. 基本保证:失败后对象处于有效但不确定的状态
  2. 强保证:失败后状态不变(事务语义)
  3. 不抛保证:绝不失败(析构函数、swap、赋值等必须提供)

第十部分:STL 容器(Ch 76-82)

编号规则核心要点
76Use vector by default. Otherwise, choose an appropriate container默认用 vector;其他容器按需选择
77Use vector and string instead of arrays用 vector 和 string 替代原生数组
78Use vector (and string::c_str) to exchange data with non-C++ APIs用 vector 与非 C++ API 交换数据(连续内存)
79Store only values and smart pointers in containers容器中只存值和智能指针(不要存裸指针表达所有权)
80Prefer push_back to other ways of expanding a sequence优先用 push_back 扩展序列
81Prefer range operations to single-element operations优先用范围操作而非单元素操作(减少重新分配)
82Use accepted idioms to really shrink capacity and really erase elements用”swap 技巧”真正收缩容量(vector<T>(v).swap(v))

Ch 76 为什么默认用 vector:连续内存 → 缓存友好 → 实际性能通常优于链表(即使理论复杂度更高)。这也是 Bjarne Stroustrup 的反复强调的观点。

第十一部分:STL 算法与谓词(Ch 83-89)

编号规则核心要点
83Use a checked STL implementation使用带检查的 STL 实现(调试版本的边界检查、迭代器检查)
84Prefer algorithm calls to handwritten loops优先用算法调用而非手写循环(更清晰、更少 bug、可能更快)
85Use the right STL search algorithm选择正确的 STL 搜索算法(find / binary_search / lower_bound…)
86Use the right STL sort algorithm选择正确的 STL 排序算法(sort / stable_sort / partial_sort / nth_element)
87Make predicates pure functions谓词必须是纯函数(无副作用、无状态、相同输入相同输出)
88Prefer function objects over functions as algorithm and comparer arguments优先用函数对象而非函数作为算法参数(可内联、可携带状态)
89Write function objects correctly正确编写函数对象(result_type 等适配,C++11 后用 lambda 简化)

第十二部分:类型安全与 C++ 安全实践(Ch 90-100)

编号规则核心要点
90Avoid type switching; prefer polymorphism避免类型开关(type-switch);用多态替代
91Rely on types, not on representations依赖类型,不依赖内存布局表示
92Avoid using reinterpret_cast避免 reinterpret_cast(最危险的转换)
93Avoid using static_cast on pointers避免对指针使用 static_cast(用 dynamic_cast 或重构)
94Avoid casting away const避免去除 const(const_cast 几乎总是设计问题的信号)
95Don’t use C-style casts不要用 C 风格强制转换(无法区分转换意图,搜索困难)
96Don’t memcpy or memcmp non-PODs不要对非 POD 类型用 memcpy/memcmp(破坏对象生命周期)
97Don’t use unions to reinterpret representation不要用 union 重新解释内存表示(类型双关是 UB)
98Don’t use varargs (ellipsis)不要用可变参数(省略号语法)— 无类型安全
99Don’t use invalid objects. Don’t use unsafe functions不要使用无效对象;不要使用不安全函数(gets、strcpy 等)
100Don’t treat arrays polymorphically不要多态地使用数组(数组索引计算会出错,因为指针算术不考虑派生类大小)

关键主题提炼

1. 渐进式约束

全书体现了一个核心理念:尽可能用编译器/类型系统约束代码,而不是靠人记住约定。

编译期检查 > 链接期检查 > 运行期检查 > 人工审查

这体现在:const、static_assert、类型系统、explicit、=delete、RAII 等。

2. C++ 的设计哲学

  • 信任程序员:给你足够的 rope(你也可能用它上吊)
  • 不为你不用的东西付费(零开销原则)
  • 与 C 兼容(但书中多处警告不要用 C 风格写 C++)

3. 与其他经典书籍的关系

  • Effective C++(Scott Meyers):本书是其补充和系统化——Meyers 是 55 条条款,本书扩展为 101 条且覆盖更广
  • Modern C++ Design(Alexandrescu):本书体现了 Alexandrescu 对泛型编程的深刻理解(Ch 64-67)
  • Exceptional C++(Herb Sutter):异常安全部分是 Sutter Guru of the Week 的精华
  • The C++ Programming Language(Stroustrup):本书是 Stroustrup C++ In-Depth Series 中的一卷

实践建议

团队落地步骤

  1. 不要全部强制:从高优先级规则开始(RAII、const、警告级别、版本控制)
  2. 用工具自动化:编译器警告、静态分析工具(clang-tidy、CppCheck)、Code Review checklist
  3. 解释为什么:每条规则都要有理由,不能是”书上说的”
  4. 允许例外:每条规则都有 Exceptions 小节——知道何时可以打破规则

最值得优先遵守的 10 条规则(个人精选)

  1. Ch 1:最高警告级别零警告通过
  2. Ch 13:使用 RAII 和智能指针
  3. Ch 15:主动使用 const
  4. Ch 50:基类析构函数要么 public+virtual 要么 protected+nonvirtual
  5. Ch 51:析构函数和 swap 绝不失败
  6. Ch 37:公有继承 = 可替换性(LSP)
  7. Ch 71:编写异常安全的代码
  8. Ch 76:默认用 vector
  9. Ch 84:用算法替代手写循环
  10. Ch 95:不要用 C 风格强制转换

关联