第5章:构造、析构与复制语义
对齐原书第5章 Semantics of Construction, Destruction, and Copy:纯虚析构与 virtual/const 契约、继承构造、vptr、复制、效率和逆序析构。
学习目标
- 能解释 pure virtual destructor、virtual specification 与 const within a virtual specification 对 class interface 和销毁安全的约束
- 能推导 object construction without/under inheritance、virtual inheritance 与 vptr initialization 的完整阶段顺序
- 能分析 object copy semantics、object efficiency 和 semantics of destruction,并定位 raw copy、构造期虚调度与非虚析构错误
机制总览
第5章:构造、析构与复制语义:机制路径
- 1
从“接口声明决定对象能否安全活完一生”开始
constructor/destructor 不是独立函数;它们维护 object 从“不存在”到“各 subobject 有效”再回到“不存在”的状态机。先预测一个带 pure virtual destructor 的 abstract base 是否可以不给 destructor body:de…
- 2
重新审视 Class Declaration
polymorphic base 若允许通过 base pointer 销毁 dynamic derived object,destructor 必须 virtual。它也可以 pure virtual 以表达 interface 不应直接实例化,但仍要定义,因为 destruction chain 总会进入 base subobject teardown。
- 3
1 Object Construction without…
对于没有 inheritance 的 abstract data type,construction 仍分为 member initialization 与 body。initializer list 直接选择 member constructors;body 运行时 members 已经有效。若 …
章级决策实验
第5章:构造、析构与复制语义:机制与证据
切换《第5章:构造、析构与复制语义》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。
选择推理阶段
当前阶段 · 从“接口声明决定对象能否安全活完一生”开始
constructor/destructor 不是独立函数;它们维护 object 从“不存在”到“各 subobject 有效”再回到“不存在”的状态机。先预测一个带 pure virtual destructor 的 abstract base 是否可以不给 destructor body:de…
可核验证据
用对象大小、成员地址、反汇编或构造析构轨迹核对「从“接口声明决定对象能否安全活完一生”开始」,并区分标准语义与当前 ABI 实现。
学完《第5章:构造、析构与复制语义》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。
失效—证据矩阵
第5章:构造、析构与复制语义:失效与核验
从“接口声明决定对象能否安全活完一生”开始
典型失效
若只从源码表面理解「从“接口声明决定对象能否安全活完一生”开始」,忽略编译器生成布局、调用约定和生命周期代码,调试时就会把实现机制误当成语言承诺。
核验证据
用对象大小、成员地址、反汇编或构造析构轨迹核对「从“接口声明决定对象能否安全活完一生”开始」,并区分标准语义与当前 ABI 实现。
重新审视 Class Declaration
典型失效
若只从源码表面理解「重新审视 Class Declaration」,忽略编译器生成布局、调用约定和生命周期代码,调试时就会把实现机制误当成语言承诺。
核验证据
用对象大小、成员地址、反汇编或构造析构轨迹核对「重新审视 Class Declaration」,并区分标准语义与当前 ABI 实现。
1 Object Construction without…
典型失效
若只从源码表面理解「1 Object Construction without…」,忽略编译器生成布局、调用约定和生命周期代码,调试时就会把实现机制误当成语言承诺。
核验证据
用对象大小、成员地址、反汇编或构造析构轨迹核对「1 Object Construction without…」,并区分标准语义与当前 ABI 实现。
从“接口声明决定对象能否安全活完一生”开始
constructor/destructor 不是独立函数;它们维护 object 从“不存在”到“各 subobject 有效”再回到“不存在”的状态机。先预测一个带 pure virtual destructor 的 abstract base 是否可以不给 destructor body:derived teardown 最终仍要调用 base destructor,所以“纯虚”禁止直接实例化,却不取消 base cleanup definition 的责任。
↡把 destructor 声明为 pure virtual,使 class 保持 abstract,同时仍必须提供可被 derived teardown 调用的定义。重新审视 Class Declaration
Presence of a Pure Virtual Destructor
polymorphic base 若允许通过 base pointer 销毁 dynamic derived object,destructor 必须 virtual。它也可以 pure virtual 以表达 interface 不应直接实例化,但仍要定义,因为 destruction chain 总会进入 base subobject teardown。
struct Shape {
virtual double area() const = 0;
virtual ~Shape() = 0;
};
Shape::~Shape() = default;
struct Circle final : Shape {
double area() const override { return 1.0; }
};Presence of a Virtual Specification
不是所有 operation 都应 virtual。virtual 适合 base contract 允许 dynamic replacement 的点;nonvirtual operation 可封装 invariant 并调用 protected virtual hook。无差别 virtual 会扩大 override surface、引入 polymorphic representation,并让 construction/destruction 期间的行为更难推导。
↡const qualifier 是 virtual member signature 与 object contract 的一部分,non-const 同名函数不会覆写 const virtual。Presence of const within a Virtual Specification
double area() const 与 double area() 是不同 member-function types。derived 必须写匹配的 const 才 override;override 关键字把错误变成 compile-time diagnostic。const member 接收 const object view,但不自动保证 thread safety,也不代表 physical bytes 永远不变。
“reconsidered class declaration” 的目标是让 destructor policy、override points、const contract 与 data ownership 一起可审计,而不是先写 hierarchy,再让 compiler-generated lifetime work 猜测设计意图。
5.1 Object Construction without Inheritance
↡单一 class object 按成员声明顺序建立 subobjects、必要 polymorphic representation,最后运行 constructor body 的过程。Abstract Data Type
对于没有 inheritance 的 abstract data type,construction 仍分为 member initialization 与 body。initializer list 直接选择 member constructors;body 运行时 members 已经有效。若 class 有 virtual functions,常见 ABI 还要在适当位置建立 vptr,使 body 中的 virtual call 表现为当前 class。
class Buffer {
public:
explicit Buffer(std::size_t size)
: data_(size), checksum_(compute(data_)) {}
private:
std::vector<std::byte> data_;
std::uint64_t checksum_;
};declaration order 是 data_ 后 checksum_,所以后者可以读取已构造 member。若反过来声明,调整 initializer list 文本顺序也不能修复。constructor body 适合执行依赖所有 members 已有效的工作,不适合用 assignment 代替必须的 direct initialization。
Preparing for Inheritance
准备继承时要问:是否会经 base pointer 删除?哪些 operation 真正需要 dynamic replacement?copy 是保留 dynamic type、clone,还是禁止 slicing?base constructor 是否调用可覆写行为?data 应 private 还是通过 protected invariant-preserving hooks 暴露?这些决定会直接改变 generated construction/destruction code。
5.2 Object Construction under Inheritance
↡most-derived object 按 virtual bases、direct bases、当前 class representation、members 与 body 的语义顺序逐层建立。derived constructor 不是只执行花括号内语句。概念展开包含:most-derived constructor 负责所有 virtual bases;direct bases 按 base-list order;implementation 建立当前 class 的 virtual state;members 按 declaration order;最后进入 body。每个 base constructor 又在自己的 subobject phase 执行同类规则。
Virtual Inheritance
↡在 diamond hierarchy 中由 most-derived constructor 构造一次 shared virtual base,并让中间 bases 复用该 subobject。中间 base 单独作为 complete object 时要负责其 virtual base;作为更大 derived 的 subobject 时,most-derived constructor 接管。旧实现可能通过 hidden flag/parameters 区分,现代 ABI 也有 construction vtables 等机制。需要掌握的是语义 ownership,不把某个 ABI 参数形式当标准。
The Semantics of the vptr Initialization
↡construction/destruction 期间让每个 active class subobject 使用与当前阶段一致的 virtual table 状态。base constructor 运行时,derived invariant 尚未建立,因此 virtual call 只表现为当前 base phase;进入 derived phase 后,vptr representation 再对应 derived overrides。complete construction 后,dynamic dispatch 才稳定指向 most-derived final overrider。destruction 则反向退回各 base phase。
qualified call 是静态选择,constructor 内的普通 virtual call 受 construction dispatch rules 限制。不要依赖从 base constructor 调到 derived override 完成初始化;那会读取尚未构造 members,语言机制正是为了阻止这种错误。
5.3 Object Copy Semantics
↡copy construction 与 copy assignment 对 bases、members、virtual bases 和 dynamic identity 应分别执行的对象级复制规则。copy construction 建立新 lifetime:virtual/direct bases 与 members 按目标对象 construction 顺序复制,target vptr 由 target construction phase 建立。copy assignment 修改已有 object:调用 base/member assignment,target dynamic identity 不因 source bytes 改变。
struct Base {
virtual ~Base() = default;
int id = 0;
};
struct Derived : Base {
std::string name;
};
Derived first;
Derived second = first; // 构造新对象
second = first; // 赋值给已有对象对 polymorphic object 执行 memcpy 会绕过 string/resource copy,并可能把 source vptr、self-pointer 或 padding 状态覆盖到 target。按 Base value copy Derived 还会 slicing。需要保留动态类型时设计 virtual clone 或 value wrapper;不允许复制时显式 delete copy operations。
5.4 Object Efficiency
↡比较 object-based 与 object-oriented 表示在 construction、copy、dispatch、virtual inheritance 和 destruction 上的可测成本。object-based value class 可拥有 direct calls、contiguous data 与 trivial operations;object-oriented hierarchy 提供 runtime substitution,常付 vptr、indirect branch、adjustment 和 nontrivial lifetime work。成本不是“class vs struct”,而是具体 semantics 的选择。
测试应分开:对象 size/alignment;批量 construction/destruction;copy/move count;direct/virtual call;single/multiple/virtual inheritance;cache locality。若 optimizer 去虚化或消除对象,说明该 workload 没保留想测的动态语义,需要通过 external target set、separate translation unit 或 profile-aware setup修正,而不是关闭所有优化制造不真实结果。
5.5 Semantics of Destruction
↡先运行当前 destructor body,再逆声明顺序销毁 members、逆序销毁 direct bases,最后由 most-derived 路径销毁 virtual bases。destruction 是 construction 的逆向状态退场,但 destructor body 运行时当前 class members 尚存在;body 结束后 members 逆序销毁,再到 direct bases,shared virtual bases 最后。每退到一个 base phase,virtual dispatch 也受当前 active class 限制。
通过 base pointer delete derived object 要求 base destructor virtual,否则 behavior undefined。destructor 默认应 noexcept;若 stack unwinding 中再次抛出未处理 exception,会终止程序。资源释放交给 member RAII owner,可让 compiler-generated destructor 正确组合顺序。
若某个 member constructor 抛出,complete object 从未构造成功,因此其 destructor body 不运行;但此前已成功的 bases/members 会逆序销毁。raw acquire 放在 body 早期却未进入 owner,可能在后续 throw 时泄漏。initializer + RAII 是正确的 failure path 表达。
第5章验证清单
- polymorphic base 明确 virtual destructor;pure virtual destructor 仍提供 definition。
- 每个 virtual operation 检查 const/ref qualifier 与
override是否精确匹配。 - 按 virtual bases、direct bases、vptr phase、members、body 推导 construction。
- 构造期 virtual call 只使用 active class,不读取未建立 derived invariant。
- copy construction 与 assignment 分开,vptr 不当业务 bytes 复制。
- 需要动态复制时定义 clone/value wrapper,避免 slicing。
- object efficiency 分别测 lifetime、dispatch、adjustment、size 和 locality。
- destructor body、members、bases、virtual bases 逆序撤场;failure 只 unwind 已构造部分。
小结
- pure virtual destructor 让 base abstract,但仍必须提供 teardown definition
- virtual specification 与 const qualifier 定义 override contract 和对象表示需求
- 无继承 construction 也要先建立 members/virtual state,再运行 body
- 有继承 construction 由 most-derived virtual bases 开始,逐层建立 direct bases、vptr、members
- vptr initialization 随 active construction/destruction phase 变化,阻止调用未就绪 derived state
- copy construction 建新 lifetime,assignment 修改现有 state,二者都不能 raw-copy polymorphic identity
- object efficiency 来自所选语义,不来自 class/struct 标签
- destruction 逆序撤销已建立 subobjects,partial construction 只 unwind 成功部分
名词解释
本章出现的专业名词,用大白话再讲一遍。
- pure virtual destructor
使 base abstract 但仍有定义的虚析构函数。
- presence of a virtual specification
以 virtual 声明动态覆写契约。
- const within a virtual specification
作为 override signature 一部分的 const。
- object construction without inheritance
建立单一 class 的 members 与 representation。
- preparing for inheritance
为 base role 重审 lifetime、copy 与 extension contract。
- object construction under inheritance
从 virtual/direct bases 到 derived body 的构造序列。
- construction under virtual inheritance
most-derived 路径构造一次 shared virtual base。
- semantics of the vptr initialization
按 active subobject phase 建立 virtual state。
- object copy semantics
构造/赋值时复制 subobjects 而保留 target identity。
- object efficiency
可测的对象表示、lifetime 与 dispatch 成本。
- semantics of destruction
body 后按逆序撤销 members 与 bases。
- partial construction unwinding
只销毁异常前成功构造的 subobjects。
练习
- 问题 1:修正 abstract base 声明。 给一个缺少 virtual destructor、
constoverride 不匹配、constructor 调用 virtual hook 的 hierarchy,逐项说明错误与修复。
- 问题 2:展开 diamond object 的构造与析构。 两个中间 bases 虚继承 Root,Leaf 同时继承它们;列出 virtual base、direct bases、members、vptr phases、bodies 与 reverse teardown。
- 问题 3:审计 polymorphic copy 与性能。 找出
memcpy、Base 按值参数、非虚析构和频繁 virtual-base access 四类问题,并给 correctness 与 measurement 方案。