C++ / a working model

THE FIELD GUIDE / 80 LESSONS

一次,弄懂一个问题。

先看结论,再读原理。运行例子,最后独立回答练习。

共 80 篇 · 阅读进度仅保存在当前浏览器

从第一篇开始 →

80 篇文章

01

语言基础 / C++11

sizeof:测量对象表示,而非运行时内容

sizeof 查询静态类型的对象表示大小,结果以 C++ 字节为单位,包含必要的填充。它通常看起来像函数调用,实际是操作数不求值的运算符;数组、引用与指针的结果必须分别理解。

约 7 分钟
02

语言基础 / C++11

sizeof 与 strlen:容量、终止符和逻辑长度

sizeof 测量类型占用,strlen 沿字符序列寻找第一个零字符,两者回答不同问题。字符串字面量包含结尾零字符,嵌入零字符会提前结束 strlen;指针本身不记录容量,也不保证指向合法字符串。

约 7 分钟
03

语言基础 / C++11

数组参数退化:声明调整与 array-to-pointer conversion

数组并不是指针,但数组形式的函数形参会被调整成指针类型,普通调用中数组实参也常转换为首元素指针。边界不会自动跟随;要保留长度,使用数组引用、容器或 C++20 的 span。

约 8 分钟
04

语言基础 / C++11

const:只读访问路径与常量对象

const 限制经由某个类型进行的修改,但只读引用不意味着底层对象永不变化。理解顶层与底层 const、成员函数的只读承诺,以及 const 与 constexpr 的区别,才能写出可靠而不过度承诺的接口。

约 8 分钟
05

语言基础 / C++11

static:存储期、链接与类成员三种语境

static 的意义取决于出现位置:局部变量获得静态存储期,命名空间实体可以获得内部链接,类的静态成员不属于某个实例。局部静态初始化的线程安全,不代表后续读写也自动线程安全。

约 8 分钟
06

语言基础 / C++11

volatile:可观察访问,不是线程同步

volatile 告诉实现相关访问具有可观察效果,不能按普通内存访问随意消除。它不提供原子性、线程间顺序或 happens-before,也不承诺绕过 CPU 缓存;线程通信应使用原子操作或锁。

约 8 分钟
07

语言基础 / C++11

内存对齐 Alignment 与结构体填充

对齐规定对象可放置的地址条件,sizeof 包含为合法布局所需的填充。alignof 查询类型要求,alignas 提高声明的对齐要求;成员大小相加不能代替布局计算,打包结构也不能代替可移植序列化。

约 8 分钟
08

语言基础 / C++11

大小端 Endianness:数值与字节顺序分离

大小端描述多字节标量在存储中的字节排列,不改变数值本身。可移植协议应显式定义字段宽度和编码顺序,再用无符号运算编解码;不要通过未对齐强转或读取未激活的联合体成员猜测端序。

约 8 分钟
09

语言基础 / C++11

class 与 struct:默认访问不同,能力相同

C++ 的 class 和 struct 都定义类类型,都支持构造、析构、继承、虚函数与模板。语言差异主要是默认成员访问和默认继承访问;用 struct 表达简单数据、用 class 维护不变量,是设计惯例而不是功能限制。

约 7 分钟
10

语言基础 / C++11

宏与 inline:文本替换不等于函数调用

函数式宏在预处理阶段替换记号,不提供参数类型、正常作用域或单次求值保证。inline 函数具有完整函数语义,主要关联多翻译单元定义规则,并不强制机器码内联;常量和计算通常优先使用 constexpr 或模板。

约 8 分钟
11

语言基础 / C++11

typedef 与 using:类型别名不是新类型

typedef 和 using 都为已有类型提供名字,不创建独立类型,也不是宏替换。using 的语法更适合复杂类型和别名模板;给指针别名添加 const 会限定整个指针类型,而不是自动限定它指向的对象。

约 7 分钟
12

语言基础 / C++11

explicit:把转换意图留在调用处

explicit 阻止构造函数或转换函数参与某些隐式转换,但仍允许直接初始化和显式转换。它适合保护单位、容量和资源包装类型;explicit operator bool 还能支持条件判断而避免意外数值转换。

约 8 分钟
13

语言基础 / C++11

extern:声明共享实体,而非重复分配

extern 常用于声明在别处定义的变量,让多个翻译单元引用同一个实体。必须区分声明和定义、作用域与链接;带初始化器的 extern 变量声明通常就是定义,头文件中的重复定义也不会被包含保护消除。

约 8 分钟
14

语言基础 / C++11

extern "C":语言链接与 C ABI 边界

extern "C" 为适用的函数和变量声明指定 C 语言链接,用于连接兼容的 C 接口。它不是把函数体切换为 C 语言,也不让任意 C++ 类型获得跨语言 ABI;名字、调用约定、布局和资源责任必须分别处理。

约 9 分钟
15

语言基础 / C++11

mutable:逻辑只读对象中的可变实现状态

mutable 允许特定非静态数据成员在所属对象为 const 时仍被修改,适合缓存、互斥量等实现细节。它不会自动提供同步,也不应掩盖逻辑状态变化;lambda 的 mutable 则控制按值捕获副本的可修改性。

约 8 分钟
16

语言基础 / C++11

四种 cast:转换意图与安全前提

static_cast、dynamic_cast、const_cast 与 reinterpret_cast 分别表达不同转换意图,但关键字本身不保证运行安全。选择前先证明数值范围、动态类型、实际可修改性,以及对象生命期、对齐和类型访问规则。

约 10 分钟
17

类与对象 / C++11

封装、继承与组合:OOP 的边界

封装维护对象的不变量,公开继承表达可替换的接口关系,组合表达拥有或使用关系。不要因为能复用几行代码就建立继承层次;先定义合法状态与调用契约,再决定是否需要运行期多态。

约 8 分钟
18

类与对象 / C++11

访问控制 Access control 与继承权限

public、protected、private 控制名字的可访问性;继承方式还控制基类接口及向上转换对外是否可用。访问检查不会替你过滤重载候选,protected 也不允许派生类通过任意基类对象访问受保护成员。

约 8 分钟
19

类与对象 / C++11

重载、覆盖、隐藏:Overload / Override / Hiding

重载在编译期从候选函数中选择签名,覆盖决定虚调用最终执行哪个实现,隐藏则发生在名字查找阶段。三者可以同时出现;使用 override 检查覆盖意图,用 using 恢复需要的基类重载集合。

约 9 分钟
20

类与对象 / C++11

构造与析构顺序 Construction order

最派生类先初始化虚基类,再按声明顺序初始化直接基类和成员,最后执行构造函数体。初始化列表的书写顺序不能改变规则;析构反向清理已完成的子对象,构造失败也依赖这一确定顺序。

约 10 分钟
21

类与对象 / C++11

析构函数、异常与 noexcept

析构负责可靠清理,不适合承担必须报告失败的业务提交。析构函数通常隐式不抛出;异常越过 noexcept 边界会终止程序,即使允许抛出,在栈展开时再次让异常逃出析构也会调用 terminate。

约 8 分钟
22

类与对象 / C++11

深浅拷贝与 Rule of Zero / Five

默认拷贝逐成员处理,是否共享底层资源取决于成员类型,而不是统一的深拷贝或浅拷贝标签。优先让标准资源类型管理所有权;只有确需自定义复制语义时,才同时设计拷贝、移动、赋值和析构。

约 10 分钟
23

类与对象 / C++11

静态与动态多态 Polymorphism、切片与 RTTI

模板让调用按编译期类型组合,虚函数让同一基类接口在运行期选择实现。按值复制为基类会切片,丢失派生部分;RTTI 能安全查询活着的多态对象,但不能修复悬空指针或替代合理的接口设计。

约 9 分钟
24

类与对象 / C++11

this、对象限定与链式接口

this 是隐式对象成员函数所操作对象的指针表达式,其指向类型随成员函数的 const 限定变化。返回 *this 可建立链式接口,但不会延长对象生命周期;静态成员函数没有 this,捕获 this 也不代表拥有对象。

约 8 分钟
25

类与对象 / C++11

虚表 Vtable:语言语义与 ABI 实现

标准规定虚调用应选择哪个最终覆盖者,却不规定虚表数量、虚指针位置或对象内存图。常见 ABI 用虚表、调整桩和 RTTI 元数据实现这些语义;理解它们有助于调试,但不可用未经保证的内存读取验证。

约 9 分钟
26

类与对象 / C++11

抽象类 Abstract class 与纯虚函数定义

抽象类不能创建完整对象,但可以拥有状态、构造函数和普通实现。纯虚表示具体派生类必须提供非纯的最终覆盖者,不等于函数不能有定义;纯虚析构在实际销毁派生对象时仍然需要可用定义。

约 8 分钟
27

类与对象 / C++14

为什么没有 virtual constructor:工厂与 clone

构造函数不能声明为虚函数;创建表达式已经决定要建立的具体类型,不能靠尚未完成的对象反向选择构造类型。运行期选型应使用工厂,按现有动态类型复制则用虚 clone,构造期间的虚调用也有阶段限制。

约 9 分钟
28

类与对象 / C++14

基类析构:public virtual 或 protected nonvirtual

如果允许通过基类指针拥有并删除派生对象,基类应提供 public virtual 析构;如果基类只是不可独立销毁的接口视图,可用 protected nonvirtual 析构阻止这种删除。选择依据是销毁契约,不是见到继承就一律加 virtual。

约 9 分钟
29

类与对象 / C++11

成员模板为何不能 virtual

成员函数模板不能声明为虚函数,它的某个特化也不会自动覆盖基类虚函数。但类模板里的普通非模板成员可以是虚函数。需要兼顾泛型入口与运行期扩展时,可把模板转换层和固定签名的虚接口分开。

约 8 分钟
30

类与对象 / C++20

空类、EBO 与 [[no_unique_address]]

空类的完整对象仍具有非零大小,以支持对象身份和数组寻址;空基类子对象则可能不占额外空间。C++20 的 no_unique_address 将重叠布局机会扩展到成员,但不保证具体 sizeof,也不取消同类型对象的身份规则。

约 8 分钟
31

内存与所有权 / C++20

内存模型与对象生存期 Object lifetime

一块地址可用的存储,不等于其中已经存在可访问的对象。判断访问是否合法,要同时检查大小、对齐、对象生存期、访问类型和边界;构造与析构决定对象的阶段,分配与释放决定底层存储的阶段。

约 10 分钟
32

内存与所有权 / C++11

指针 Pointer:地址、边界与借用

指针是有类型的值,可能指向对象、函数、尾后位置,也可能为空或无效。非空不是可解引用的证明;正确使用依赖对象仍存活、类型匹配和访问未越界,裸指针本身也不表达释放责任。

约 8 分钟
33

内存与所有权 / C++11

读懂指针声明 Pointer declarators

读复杂声明要从名字出发,按括号与声明符的结合关系分层分析。数组指针、指针数组、函数指针并不等价;const 修饰哪一层也决定能否改指针或目标,用类型别名可以让接口更清晰。

约 9 分钟
34

内存与所有权 / C++14

存储期 Storage duration 不等于内存分区

C++ 规定自动、静态、线程和动态四种存储期,不要求固定的栈、堆或可执行文件分段。变量名的作用域、名字的链接属性、对象的生存期和操作系统映射分别回答不同问题,不能混成一张地址图。

约 9 分钟
35

内存与所有权 / C++11

引用 Reference 与临时对象期限

引用提供已有对象的别名,不是可重新绑定的普通指针,也不自动拥有目标。const 引用可以在特定初始化场景延长临时对象的生存期,但这种延长不会沿参数、返回值或另一个引用任意传递。

约 9 分钟
36

内存与所有权 / C++14

参数传递 Parameter passing 与所有权契约

选参数类型时先决定函数只是读取、需要修改,还是接收所有权,再比较复制成本。小值按值,大型只读对象用 const 引用,可空借用用指针,独占所有权按值传 unique_ptr;这些类型传达的是不同契约。

约 9 分钟
37

内存与所有权 / C++14

RAII:把清理责任交给对象

RAII 把资源的获取与释放封装进拥有型对象,用确定的析构时机处理正常返回和异常展开。关键不是“资源必须在栈上”,而是每份资源有明确拥有者,构造失败不泄漏,析构不把失败继续向外抛。

约 10 分钟
38

内存与所有权 / C++14

智能指针选择 Ownership vocabulary

先考虑直接值成员和容器;确需动态生存期时,默认用 unique_ptr 表达独占拥有,仅在多个参与者都必须延长生存期时使用 shared_ptr。观察者采用引用、裸指针或 weak_ptr,不要把所有访问都升级为拥有。

约 9 分钟
39

内存与所有权 / C++11

shared_ptr:控制块与线程安全边界

shared_ptr 共享的是一组销毁责任,通常通过控制块记录拥有者、删除器等信息。控制块允许不同 shared_ptr 实例并发管理同一对象,但不保护对象内部数据,也不允许无同步地修改同一个 shared_ptr 变量。

约 11 分钟
40

内存与所有权 / C++11

weak_ptr:打破环并安全取得临时拥有权

weak_ptr 观察已有 shared_ptr 所有权组而不增加强引用数量,适合回指、订阅者和非拥有缓存。访问前直接 lock,成功得到的 shared_ptr 会保护这次使用的生存期;expired 检查本身不能预留对象。

约 10 分钟
41

内存与所有权 / C++11

malloc 与分配器 Allocator 的职责

malloc 提供未初始化存储,失败返回空指针,必须与 free 配对;它不运行 C++ 构造函数。分配器可能缓存、分桶或向系统申请映射,具体实现并非语言保证,申请字节与构造对象应当分层理解。

约 11 分钟
42

内存与所有权 / C++11

new/delete 表达式与分配函数

new 表达式通常包含取得存储和初始化对象,delete 表达式通常包含析构和释放存储;operator new/operator delete 是其中的底层分配函数,不等同于完整表达式。理解这层区别才能正确处理异常、数组和原位构造。

约 11 分钟
43

内存与所有权 / C++14

内存泄漏 Memory leaks:从责任到证据

泄漏首先是资源没有在约定时间被正确回收,不是简单观察进程内存有没有下降。用 RAII 覆盖异常路径,再结合 LeakSanitizer 的分配栈与重复工作负载区分失联分配、引用环、无界缓存以及分配器保留。

约 10 分钟
44

内存与所有权 / C++11

悬空与未初始化指针 Dangling pointers

未初始化指针没有可依赖的指针值;悬空指针则曾经指向有效对象,但目标已经销毁或失效。初始化为 nullptr 只解决起始状态,不能跟踪对象生存期;根本办法是约束借用范围并正确安排拥有关系。

约 9 分钟
45

内存与所有权 / C++11

内存错误排查 Memory safety diagnostics

把内存错误拆成空间越界、时间失效、初始化缺失、释放协议错误和并发竞争,再选择对应证据。崩溃地点往往只是后果;应追踪第一次非法访问、分配与释放栈,并以明确边界和拥有型接口修复根因。

约 11 分钟
46

现代 C++ / C++11

用 nullptr 表达空指针

nullptr 是具有独立类型的空指针字面量,不是整数零,也不是某种通用指针。它让重载选择和模板传参保留空指针语义,但不会让解引用变安全,更不能代替对象生命周期管理。

约 7 分钟
47

现代 C++ / C++14

auto 与 decltype(auto) 的推导边界

auto 让编译器推导静态类型,并不引入动态类型。是否保留引用与 const 取决于声明形式;decltype(auto) 则直接采用 decltype 规则,括号也能改变结果,尤其影响函数返回值与生命周期。

约 9 分钟
48

现代 C++ / C++11

值类别与 std::move:转换不等于搬运

左值、将亡值与纯右值描述表达式,而不是变量永久拥有的标签。std::move 只把表达式转换为可参与移动重载的形式;实际是否转移资源、源对象留下什么状态,取决于被调用类型的契约。

约 9 分钟
49

现代 C++ / C++11

转发引用与 perfect forwarding

转发引用配合模板推导记录调用者传入的值类别,std::forward 再把这份信息用于下一次调用。它适合透明包装,不是所有 T&& 的别名,也不是比 std::move 更高级的通用替代品。

约 10 分钟
50

现代 C++ / C++14

Lambda 捕获:闭包也是有生命期的对象

Lambda 创建带调用运算符的闭包对象,捕获决定它保存值还是借用外部状态。按值、按引用与初始化捕获有不同的所有权后果;把回调存起来之前,应先证明它依赖的对象能活到调用结束。

约 9 分钟
51

现代 C++ / C++11

模板推导:先匹配参数,再实例化代码

模板是一组由参数生成的声明与实现,不是把任意实参强行转换成同一种类型。理解按值与按引用推导、非类型参数以及定义可见性,能更快解释调用失败、数组退化和依赖名称错误。

约 10 分钟
52

现代 C++ / C++11

全特化、偏特化与重载不要混用

全特化为确定的模板实参提供替代定义,偏特化为一类实参模式提供实现。类模板可以偏特化,函数模板不能;函数行为的分流通常优先用重载或约束,避免误判特化参与选择的方式。

约 10 分钟
53

现代 C++ / C++17

Type traits 与 if constexpr 的编译期分支

Type traits 把类型性质表达为编译期值或类型变换;if constexpr 在模板实例化时丢弃不适用的分支。它解决的是不同类型需要不同合法表达式的问题,不会把运行期条件变成编译期事实。

约 9 分钟
54

现代 C++ / C++20

constexpr、consteval 与 constinit 各管什么

constexpr 表达常量求值的能力与变量约束,consteval 要求立即调用满足常量表达式条件,constinit 约束静态或线程存储期变量的初始化。三者并不等价,尤其 constinit 不会使变量只读。

约 9 分钟
55

现代 C++ / C++17

参数包与 fold expression:先处理空包

可变参数模板保留每个实参的类型,折叠表达式把参数包组合为一个表达式。可靠的设计需要先决定空包结果、初始值类型与求值顺序,不能把省略号当成自动安全的循环或任意长度数组。

约 9 分钟
56

现代 C++ / C++17

optional 与 variant:把状态放进类型

optional 表达一个值可能不存在,variant 表达有限候选类型中的一个。二者都管理所含对象的生命周期;安全使用的关键是先确定当前状态,再访问正确分支,而不是用魔法数或裸 union 偷渡状态。

约 10 分钟
57

现代 C++ / C++20

string_view 与 span:轻量借用不延寿

string_view 与 span 把一段连续数据的地址和范围交给调用者,不拥有数据。它们能减少复制并统一接口,但原对象销毁、重新分配或失效后,视图也会失效;只读视图同样不能逃离生命周期约束。

约 10 分钟
58

现代 C++ / C++20

Concepts 与 requires:约束可调用的接口

C++20 concepts 把模板所需的类型性质和表达式条件写到接口上。requires 表达式检查是否存在合法操作,requires 子句控制候选资格;它们改善诊断与重载选择,但不能证明运行期输入或业务语义正确。

约 10 分钟
59

现代 C++ / C++20

Ranges 与惰性 views:管道不是结果缓存

C++20 ranges 把范围作为算法输入,views 可组合筛选、映射等惰性操作。创建管道通常没有计算全部结果,重复遍历也不承诺复用结果;理解底层所有权、迭代能力与最终物化边界比管道语法更重要。

约 10 分钟
60

现代 C++ / C++20

Coroutines:暂停、恢复与协程帧所有权

C++20 协程让函数暂停后恢复,但不会自动创建线程、事件循环或后台任务。最小可用抽象也必须明确管理协程帧、完成状态与异常;下面的同步生成器用独占 RAII 所有权保证提前结束时仍能释放资源。

约 15 分钟
61

现代 C++ / C++23

C++23 expected 与按特性确认可用性

std::expected<T,E> 把成功值与失败原因作为同一个返回类型交付,适合预期内、需要调用者处理的失败。启用 C++23 模式不代表整个标准库已实现,应同时检查编译器、标准库版本和对应特性宏。

约 10 分钟
62

标准库与算法 / C++11

vector:增长、容量与失效规则

vector 提供连续存储和常数时间下标访问,但容量不等于已构造元素数。理解 reserve、resize 与重分配的区别,才能正确估算追加成本,并避免把旧指针、迭代器和尾后位置带过修改操作。

约 9 分钟
63

标准库与算法 / C++11

deque:双端操作与稳定性的边界

deque 支持常数时间随机访问和两端单元素插入,却不保证连续存储。它的关键细节是引用稳定不代表迭代器稳定:两端插入保留已有元素引用,但会使迭代器失效,删除规则还要区分发生位置。

约 8 分钟
64

标准库与算法 / C++11

list:节点稳定、splice 与局部性

list 擅长已知位置的插入、删除和节点转移,而不是快速寻找位置。splice 可以保留元素身份与迭代器,但分配器和范围有前置条件;跨列表的范围转移也不能一概声称为常数时间。

约 9 分钟
65

标准库与算法 / C++11

map 与 set:有序关联和比较器契约

map、set 按比较器维护有序键,键是否重复由比较等价性决定,而不必由 operator== 决定。理解对数查找、成员边界查询和不可随意修改的键,比记住某种树的实现名称更重要。

约 9 分钟
66

标准库与算法 / C++11

unordered:哈希、等价与 rehash

unordered 容器用哈希定位候选桶,再用相等谓词识别键。平均常数查找不等于最坏常数;预留元素规模可以减少 rehash,但引用稳定、迭代器失效和桶数策略仍须分别理解。

约 9 分钟
67

标准库与算法 / C++20

迭代器:能力类别、范围与有效性

迭代器类别描述可以执行哪些操作以及相应复杂度,不负责延长对象生命周期。区分单趟输入、多趟前向、双向、随机访问和连续迭代器,再单独检查容器修改导致的失效,才能正确组合算法。

约 10 分钟
68

标准库与算法 / C++11

算法:sort、边界查找与 erase-remove

标准算法操作范围而非容器所有权。排序需要合法的严格弱序,二分边界查找依赖分区条件,remove 只改变逻辑末尾而不缩小容器;理解这些前置条件比背函数名更能避免错误。

约 10 分钟
69

标准库与算法 / C++11

容器适配器:栈、队列与堆优先级

stack、queue 和 priority_queue 用受限接口表达访问纪律。优先队列不是有序数组:比较器定义谁排在谁之前,而 top 取比较顺序中的最大项;理解这个方向才能正确实现小顶堆和多字段优先级。

约 9 分钟
70

标准库与算法 / C++17

Allocator 与 pmr:资源选择和生命周期

分配器把容器的存储获取策略与元素管理分开。C++17 pmr 允许运行时选择内存资源,但资源并不由容器共享拥有;缓冲区、资源和使用它们的对象必须按正确顺序生存与销毁。

约 10 分钟
71

并发与同步 / C++20

线程生命周期:thread、jthread 与 stop_token

线程对象管理执行资源,不等于线程正在运行。先确定谁负责等待结束,再确定共享对象活多久;C++20 的 jthread 自动请求停止并等待,但停止仍需要工作函数合作,不能强制终止阻塞操作。

约 10 分钟
72

并发与同步 / C++17

互斥与 RAII:mutex、scoped_lock 和死锁

互斥锁保护的是一组共享状态的不变量,而不是变量名字。用 RAII 绑定锁的生命周期;同时更新多个对象时,一次获取所需锁。避免锁顺序环路,也避免在持锁期间等待线程或调用未知代码。

约 10 分钟
73

并发与同步 / C++11

条件变量:谓词、丢失通知与虚假唤醒

条件变量只负责让线程等待和重新检查,不保存事件。把真实条件存进共享状态,在同一互斥锁下修改与检查,并使用带谓词的 wait;这样通知提前发生或出现虚假唤醒,都不会破坏业务逻辑。

约 11 分钟
74

并发与同步 / C++11

原子操作:data race、RMW 与无锁边界

atomic 解决单个原子对象访问不可分割的问题,不会自动把多步业务变成事务。计数更新需要 fetch_add 等读改写操作;原子类型也不承诺一定无锁,更不保证任何基于它的算法都无等待。

约 10 分钟
75

并发与同步 / C++20

内存序:relaxed、acquire-release 与 happens-before

内存序描述原子操作如何约束周围访问。relaxed 保留原子性却不发布普通数据;读取到对应 release 写入的 acquire 才建立跨线程同步。判断正确性要画出先行发生链,不能依赖机器上看似稳定的执行顺序。

约 13 分钟
76

并发与同步 / C++11

任务结果:async、future、promise 与启动策略

future 是一次性结果的接收端,不等于后台线程。async 的启动策略决定独立执行还是延迟求值;promise 负责手动提交值或异常。既要处理结果,也要明确等待、异常传播以及关联任务何时结束。

约 11 分钟
77

构建与诊断 / C++11

从源文件到程序:Compilation 与 Linking

编译器能看见声明,不等于链接器能找到定义。按预处理、编译、汇编、链接定位错误,再用一个本地三文件项目理解静态库、动态库与 CMake 目标依赖;这些构建方式属于工具链约定,不是语言规定的文件格式。

约 13 分钟
78

构建与诊断 / C++17

ODR、头文件定义与 inline 的真正作用

头文件可以被多个翻译单元包含,但其中的定义必须满足单一定义规则。inline 主要解决跨翻译单元的定义与实体身份,不是强制展开调用;模板可见性、名字查找和构建宏的一致性同样属于接口正确性。

约 12 分钟
79

构建与诊断 / C++11

异常安全:Guarantees、noexcept 与提交点

异常安全关心失败以后对象还剩什么,而不是代码里有没有 catch。用 RAII 保住资源,用先准备再提交实现强保证,并让 noexcept 只描述真正不会向外传播异常的操作;copy-and-swap 是有成本和前提的策略,不是万能答案。

约 12 分钟
80

构建与诊断 / C++11

诊断与测量:Warnings、Sanitizers 与 Debugger

让编译警告、运行时插桩和调试器分别回答不同的问题,再以可重复的输入检验修复。必须区分未定义、未指定和实现定义行为;Sanitizer 没有报告只说明本次执行未触发已启用的检查,性能结论也必须来自独立测量。

约 14 分钟