Effective Modern C++ 全书复习与迁移验收
用跨章节 failure scenarios 串联 42 个 Items:从类型和对象契约,经 ownership、move/forward、closure lifetime,走到 task outcome、memory ordering 与条件化优化。
学习目标
- 能分析一个现代 C++ API 的类型、对象、ownership、value category、closure 和 execution 六层 contract
- 能复现 copy、dangling、data race 与 allocation symptoms,并定位相关 Items
- 能设计 C++98/旧 C++ 模块迁移顺序,以负例、failure injection 和 measurement 验收
从“42 条要在同一条调用链相遇”开始
真实代码不会按章节出现。一个把 unique_ptr 移入 generic lambda、由 std::async 执行并写入 atomic state 的 API,同时涉及 type deduction、ownership、move、capture、launch policy、future lifetime 和 memory ordering。
先预测每一层最可能丢失什么信息,再用图中的 proof question 自查。
第一层:类型边界是否与意图一致
Items 1-6 要求先写出 deduced T、parameter、expression 与 return type。auto 不保证得到 owning value,decltype((x)) 可能变成 reference,proxy 可越过 source lifetime。
验收:用 static_assert、deleted overload 或 compiler diagnostic 证明 exact type;同时测试 const lvalue、mutable lvalue、rvalue、array/function 与 braced list。
第二层:对象声明能否维持有效状态
Items 7-17 把 braces、nullptr、alias、enum class、delete、override、const iterator、noexcept、constexpr、thread-safe const 和 special members 连接起来。
↡constructor、special members、inheritance specifiers 与 exception contract 共同保证对象始终满足不变量。一个 user-declared destructor 可能抑制 move;缺少 noexcept 可能让 vector 复制;logical const cache 在并发下仍需 mutex/atomic。review 必须检查 generated operations,而不是只看手写函数。
第三层:谁拥有资源直到何时
Items 18-22 先画 ownership graph:unique owner 用 unique_ptr;shared lifetime 才用 shared_ptr;observer/cycle edge 用 weak_ptr;make 函数和 Pimpl 还涉及 allocation 与 complete type。
↡资源从创建、转移、共享、观察到最终释放的 owner/observer 有向图。验收必须包含 destructor counters、cycle test、expired weak access、duplicate control-block negative test 和 exception injection。
std::shared_ptr<Node> first = std::make_shared<Node>();
std::shared_ptr<Node> second(first.get()); // negative test: two control blocks第四层:copy、move 与 forward 是否选对
Items 23-30 要区分 named rvalue-reference expression、forwarding reference 与 rvalue reference。std::move 不移动,std::forward 也不能为 braced list/overload set 发明类型。
第五层:closure state 是否晚于依赖对象
Items 31-34 要把 capture list 当 dependency manifest。默认引用可能悬空;默认值可能只复制 this pointer;init capture 可拥有 move-only state;generic lambda 用 std::forward<decltype(param)>。
测试 immediate call 不够,必须覆盖 return/store/queue/thread 后的 delayed invocation,并销毁原 owner 再运行 negative case。
auto makeDangling = [] {
int local = 42;
return [&] { return local; }; // delayed invocation is invalid
};第六层:execution outcome 与同步是否闭环
Items 35-40 从 task-based programming 开始:future 交付 value/exception;必须异步时指定 launch::async;thread 所有路径 unjoinable;析构行为按 handle/state/policy 分析;void future 做 one-shot event;atomic 负责 concurrency,volatile 负责 special memory。
↡task 的启动、进度、value/exception、等待、取消和 shared-state 销毁组成的完整执行协议。验收包括 deferred path、thread creation failure、early return/throw、last-future destructor wait、broken promise、release/acquire 和 data-race negative tests。
综合案例一:异步上传 API
template<typename Payload>
auto upload(Payload&& payload) {
auto owned = std::make_shared<Job>(
std::forward<Payload>(payload));
return std::async(std::launch::async, [owned] {
return owned->run();
});
}逐项证明:Payload deduction 不被 greedy overload 劫持;Job construction exception 不泄漏;shared_ptr 是否真的需要 shared ownership;capture 不依赖 this;future 被保存/get;run exception 被观察;future/member destruction order 不悬空。
综合案例二:线程安全缓存
const member lazy-build cache 涉及 Item 16 与 40;若 cache value 是 shared_ptr 又涉及 19/20;若后台 task 初始化则涉及 35-39。
↡多个 readers 观察一次初始化或更新结果时,对 state ownership 与 publication ordering 的联合证明。不要只把 ready 改 atomic。普通 data 必须由 release/acquire、call_once 或 mutex 安全发布;失败/重试状态也要编码,owner destruction 必须等待 background work。
综合案例三:性能 API 反而更慢
Items 29、41、42 共同要求完整成本路径:move 可能逐元素;pass-by-value assignment 可能错过容量复用;emplace 在 duplicate rejection 时可能 speculative construct。
↡把 parameter construction、allocation、container acceptance、copy/move、destruction 与 exception path 全部计入的成本模型。benchmark 必须分 lvalue/rvalue、small/large、capacity hit/miss、successful/rejected insertion,并使用 counting allocator 与 constructor counters。
迁移旧模块的正确顺序
↡先盘点资源和行为,再收紧类型/ownership,随后引入 move/concurrency,最后测量优化的迁移策略。直接全局替换 new→make_shared、push→emplace、thread→async 会改变 ownership、constructor selection 和 scheduling。每一步只改变一个 contract,并保留 before/after evidence。
五类必留证据
- Type evidence:static_assert、selected overload、compile-fail case。
- Lifetime evidence:destructor counters、owner graph、delayed invocation。
- Failure evidence:allocation/thread failure、throw、early return、broken promise。
- Concurrency evidence:happens-before explanation、TSAN/stress、deferred path。
- Cost evidence:allocation/copy/move counters 与 representative benchmark。
小结
- 全书复习应沿真实调用链串联类型、对象、ownership、category、closure 与 execution
- deduction/static_assert 证明输入边界;special-member/noexcept tests 证明对象契约
- ownership graph 与 delayed invocation 共同证明资源和 closure lifetime
- move/forward 要用 selected overload 和完整成本路径验证,不能靠关键词判断
- future/thread/atomic 要覆盖 deferred、异常、析构等待与 happens-before
- Items 41-42 只在语义正确后按 measurement 采用
- 旧模块按 contract-first 顺序迁移,每步留下五类证据
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 跨章节 contract 链
从输入到销毁逐层证明的审查。
- 类型推导边界
决定具体类型和 conversion 的位置。
- 现代对象契约
声明和特殊成员维持对象不变量。
- ownership graph
资源 owner/observer 生命周期图。
- value-category path
category 保留到最终调用的路径。
- closure dependency manifest
capture state 与 referent 寿命协议。
- task outcome protocol
执行结果错误和等待的协议。
- 异步 ownership handoff
资源转入 async task 的综合边界。
- cache publication contract
缓存 state 与可见性联合约束。
- 端到端成本路径
计入完整构造分配与拒绝的模型。
- contract-first migration
先正确后并发再优化的迁移顺序。
- 现代 C++ 迁移证据包
- 五维迁移验收集合。
练习
- 问题 1:generic upload 把 caller payload 移入 async lambda。 写出六层 review evidence。
- 问题 2:const cache 用 relaxed atomic ready 发布普通 map。 找出错误并修复。
- 问题 3:团队计划全局把 push_back 改 emplace_back、setter 改按值。 设计回归矩阵。