Effective C++ 第三版总复习
串联第三版 55 Items:从对象有效性、RAII、接口与异常安全,到多态、templates、allocation、warnings 和库生态,形成跨条款诊断与工程验收链。
学习目标
- 能解释 55 Items 如何把对象有效性、ownership、接口、实现、多态和 generic/system contract 串成一条正确性主线
- 能分析 resource、dispatch、template、build、allocator 与 portability 故障,并定位需要联合使用的 Items
- 能设计 compile、failure injection、sanitizer、performance 和 review trace 组成的整书验收
从“55 条共同保护什么”开始
整书不是 55 个互不相干的 style rules。它们共同把一个程序从“依赖开发者记得做对”改成“对象、类型和工具链默认阻止做错”。
↡对象从构造完成到析构开始始终满足 class 不变量和可用 contract。 ↡用语言结构、类型和自动生命周期机制承载正确性,而不是手工控制流约定。对象语义是所有后续原则的地基
Items 1-12 要求正确初始化、理解 compiler-generated functions、显式控制 copy、virtual destructor、destructor exception、constructor/destructor dispatch 与 assignment。
class Connection {
public:
explicit Connection(Socket socket) : socket_(std::move(socket)) {}
Connection(const Connection&) = delete;
Connection& operator=(const Connection&) = delete;
Connection(Connection&&) noexcept = default;
private:
Socket socket_;
};若对象自身 copy/destruction 不明确,RAII wrapper、container、inheritance 和 exception safety 都无法可靠建立。
↡编译器隐式生成的行为与类型真实 ownership/identity 规则不一致的风险。RAII 把失败路径纳入正常结构
Items 13-17 与 29 的组合不是“使用 smart pointer”这么窄,而是确保每项资源在所有 control-flow exits 上释放一次,operation 失败后状态满足承诺。
↡资源获取后立即进入 owner,作用域展开自动调用配对释放。 ↡操作抛异常后不泄漏,并按 basic、strong 或 no-throw 保持状态。Document load(Path path) {
File file = File::open(path);
Buffer data = readAll(file);
Document candidate = parse(data);
validate(candidate);
return candidate;
}每个 local owner 自动回收;最终状态只在 parse/validate 成功后返回。用第 N 步 failure injection 验证回滚,而不是只测 happy path。
接口把错误挡在 representation 之外
Items 18-25 要求易正确接口、把 class design 当 type design、合理传值、避免返回 local/internal handles、private data、non-member non-friend、对称 conversion 和 swap。
↡调用者只能通过保持不变量的 operations 改变对象,无法绕过 owner 直接写 representation。 ↡能够访问 private representation 的函数和类型集合。审查 API 时先问:非法 state 能否构造;borrowed handle 能否悬空;implicit conversion 是否无损;错误是否在 compile time 暴露。
实现细节不能泄漏成客户 contract
Items 26-31 处理变量生命周期、casts、internal handles、exception safety、inline 和 compilation dependencies。
↡内部 representation、地址或 iterator 被客户持有,限制未来修改并引入悬空风险。 ↡header 改动通过 include graph 触发大范围重编译和 ABI 耦合。Pimpl/interface 不是为了隐藏代码而隐藏,而是稳定客户看到的 contract;inline/cast 也需要 profiling 和语义证据。
继承审查从 substitutability 开始
Items 32-40 的核心问题不是“virtual 怎么写”,而是每条 inheritance edge 表达什么关系。
↡derived 在任何 base client 中替换 base 时保持其语义和不变量。 ↡函数 body 按动态类型选择,但 default argument 按 call expression 静态类型补入的差异。class Shape {
public:
void draw(Color color = Color::red) const { doDraw(color); }
private:
virtual void doDraw(Color color) const = 0;
};NVI 统一 default、检查和流程,virtual hook 只选择变化算法。composition 表达 has-a/implemented-in-terms-of;private/multiple inheritance 需 protected/virtual/EBO 或多身份证据。
template 错误要按阶段诊断
Items 41-48 展示 implicit interface、typename、dependent base lookup、code bloat factoring、member templates、hidden friend、traits/tag dispatch 与 TMP。
↡模板正文中必须有效的完整 expressions 集合。 ↡把 template error 分成名字查找、参数推导、substitution、conversion 和 instantiation 的定位模型。template<class Iter>
void advanceFast(Iter& iter, std::ptrdiff_t n) {
using Category = typename std::iterator_traits<Iter>::iterator_category;
doAdvance(iter, n, Category{});
}traits 提供类型事实,tag/concept 选择实现;constraints 应只表达算法真实使用,避免 overconstraint。重模板还要测 compile time、symbols 和 binary size。
allocation contract 是成对的系统
Items 49-52 把 handler、替换动机、常规和 placement failure 串起来。
↡size、alignment、failure、handler、family 和 matching delete 组成的完整分配边界。 ↡placement new 构造失败时按额外参数找到对应 delete 回收 storage。allocator 测试必须覆盖 zero bytes、OOM、handler retry、Derived size、null delete、aligned/array/sized forms 和 constructor exception。
工具链和库是 contract 的最后一层
Items 53-55 要求把 warnings 当语义信号,熟悉标准库/TR1/Boost,并用不同 compiler/library 验证 portability。
↡warning profile、零新增基线和多 compiler jobs 共同组成的诊断政策。 ↡先查标准 facility,再按缺口审查 Boost/third-party component,并隔离 vendor API。warning-free 不是正确性证明,库进入 std 也不保证所有实现性能相同;仍需 contract test 和 benchmark。
从症状映射到跨 Item 根因
真实事故往往跨章:double free 可能同时涉及 Item 11 self-assignment、Item 13 RAII、Item 14 copying behavior 和 Item 29 exception safety。不要找到一条相关 Item 就停止。
↡从可观察故障同时追踪对象、ownership、接口和工具链多个原则的分析。一次完整代码审查
按以下顺序审查一个 subsystem:
- 每个 type 的 invariant、special members 和 destruction 是否明确。
- 每项 resource 的 owner/borrow/release graph 是否闭合。
- public API 是否暴露 representation、危险 conversion 或悬空 handle。
- failure 是否有 explicit guarantee 和 rollback evidence。
- inheritance 是否满足 substitutability,static/dynamic binding 是否一致。
- templates/allocation/toolchain 是否有 constraints、cost 和 portability gates。
整书验收门禁
↡将 Item 原则映射到具体代码位置、测试、diagnostic 和责任人的记录。验收必须包含 positive/negative compile tests、failure injection、sanitizers、performance budgets 和 multi-toolchain builds。一次运行通过只证明一个路径。
先预测一个故障会在哪一层被拦截,再执行门禁;若只能在 production 发现,说明 contract 仍未机制化。
小结
- 55 Items 共同把正确性从人的记忆迁入对象、ownership、接口、类型和工具链
- 对象语义是 RAII、异常安全、继承和泛型的前置
- 真实故障要建立跨 Item 根因链,不能用单条口号解释完毕
- 整书掌握以代码审查迁移和六层 evidence gate 验收,而不是背诵条款
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 对象有效性
- 对象生命周期内持续满足不变量。
- 机制化正确性
- 由语言和类型自动承载正确性。
- 对象语义合同
特殊成员共同定义值与生命周期。
- 默认生成偏差
生成行为与真实 ownership 不一致。
- RAII ownership
资源立即进入自动释放 owner。
- 失败后状态保证
- 异常后资源和状态层级承诺。
- 接口不变量边界
- 只有合法操作可修改对象。
- 封装破坏面
可访问 representation 的代码集合。
- 实现句柄泄漏
内部地址或 iterator 逃逸给客户。
- 依赖传播成本
header 变化造成重编译与 ABI 耦合。
- substitutability
derived 保持 base contract。
- 混合绑定
default 静态、virtual body 动态选择。
- generic expression contract
模板必须成立的表达式集合。
- 模板阶段诊断
按查找推导替换转换实例化定位。
- allocation contract surface
分配失败对齐和配对完整边界。
- 构造失败配对
placement new 对应失败 delete。
- 编译诊断门禁
- warning 与多工具链构建政策。
- 库选型边界
- 标准优先和第三方隔离原则。
- 跨 Item 根因链
同时追踪多条原则的事故分析。
- 六层代码审查
- 对象到系统层的固定审查顺序。
- 条款追踪矩阵
原则到代码、测试和 owner 的映射。
练习
- 问题 1:分析 shared resource 的 double free。 copy assignment 在资源获取失败时发生事故。
- 问题 2:derived 在本地测试正确,经 Base pointer 行为错误。 设计诊断。
- 问题 3:制定一个 subsystem 的整书验收。 它包含 templates、custom allocator 和第三方 library。