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 连接起来。

一个 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。

验收必须包含 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。

验收包括 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。

不要只把 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。

benchmark 必须分 lvalue/rvalue、small/large、capacity hit/miss、successful/rejected insertion,并使用 counting allocator 与 constructor counters。

迁移旧模块的正确顺序

直接全局替换 new→make_sharedpush→emplacethread→async 会改变 ownership、constructor selection 和 scheduling。每一步只改变一个 contract,并保留 before/after evidence。

五类必留证据

  1. Type evidence:static_assert、selected overload、compile-fail case。
  2. Lifetime evidence:destructor counters、owner graph、delayed invocation。
  3. Failure evidence:allocation/thread failure、throw、early return、broken promise。
  4. Concurrency evidence:happens-before explanation、TSAN/stress、deferred path。
  5. 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 顺序迁移,每步留下五类证据

资料与写作方式声明

本章以Effective Modern C++, Items 1-42权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

名词解释

本章出现的专业名词,用大白话再讲一遍。

跨章节 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. 问题 1:generic upload 把 caller payload 移入 async lambda。 写出六层 review evidence。
  1. 问题 2:const cache 用 relaxed atomic ready 发布普通 map。 找出错误并修复。
  1. 问题 3:团队计划全局把 push_back 改 emplace_back、setter 改按值。 设计回归矩阵。

讨论

评论区加载中…