火柴人战争遗产3
413.8MB · 2026-02-21
目标:用 7 天时间,从“最简引用计数”迭代到接近 Boost shared_ptr 的控制块架构:默认删除器、自定义删除器、线程安全、weak_ptr、make_shared、最终工程化。
Day 02 只做一件事:把 Day01 的 ptr_ + count_ 最小实现,重构为 控制块(control block)模式:引入 sp_counted_base + shared_count,让 shared_ptr<T> 只保留 T* ptr_ + shared_count pn_。
今天我们要将第 1 天的简单实现重构为 Boost 的经典架构:
sp_counted_base(控制块基类)sp_counted_impl_p<Y>shared_count 辅助类管理控制块生命周期shared_ptr:由“计数指针”改为“控制块架构”核心收获:掌握 Boost 的多态控制块设计,为 Day03 的自定义删除器打下基础。
Day01 的结构:
template<typename T>
class shared_ptr {
T* ptr_;
long* count_; // 只能管理 new 分配的对象
};
局限:
delete 释放资源delete[])fclose)核心思想:把“如何释放资源”的逻辑封装到控制块里,用虚函数实现多态。
第 1 天架构: 第 2 天架构:
───────────── ─────────────
shared_ptr shared_ptr
├─ T* ptr_ ├─ T* ptr_
└─ long* count_ └─ shared_count
└─ sp_counted_base*
├─ use_count_
├─ weak_count_
└─ virtual dispose() = 0
▲
┌──────────────┴──────────────┐
sp_counted_impl_p sp_counted_impl_pd<Y,D>
(默认 delete) (自定义删除器:Day03)
┌─────────────────────────────────────────────────────────────┐
│ shared_ptr<Widget> │
│ ┌────────────────────┬─────────────────────────────────┐ │
│ │ Widget* ptr_ │ shared_count pn_ │ │
│ └────────┬───────────┴──────────┬──────────────────────┘ │
└───────────┼──────────────────────┼──────────────────────────┘
│ │
│ └─────────────────┐
▼ ▼
┌────────────────┐ ┌─────────────────────────────────┐
│ Widget 对象 │ │ sp_counted_base (抽象类) │
│ (堆内存) │ │ ┌───────────────────────────┐ │
└────────────────┘ │ │ long use_count_ = 1 │ │
│ │ long weak_count_ = 1 │ │
│ ├───────────────────────────┤ │
│ │ virtual void dispose()=0 │ │ ◄── 纯虚函数
│ │ virtual void destroy() │ │
│ └───────────────────────────┘ │
└──────────────▲──────────────────┘
│ 继承
┌───────────────────────┴───────────────────────┐
│ │
┌────────────────┴────────────────┐ ┌──────────────────┴────────────┐
│ sp_counted_impl_p<Widget> │ │ sp_counted_impl_pd<Y,D> │
│ (默认删除器:delete) │ │ (自定义删除器:Day03) │
│ ┌──────────────────────────┐ │ │ │
│ │ Widget* px_ │ │ │ 下一天讲解... │
│ │ void dispose() override │ │ └───────────────────────────────┘
│ │ { delete px_; } │ │
│ └──────────────────────────┘ │
└─────────────────────────────────┘
sp_counted_base 做三件事:
use_count_ / weak_count_dispose()destroy()最关键的区别:
dispose():释放“被管理的对象”(use_count 归零触发)destroy():释放“控制块自己”(weak_count 归零触发,默认 delete this)同时,Day02 引入了经典不变量:
典型链路(只有 shared_ptr,没有 weak_ptr):
use=1, weak=1use→0dispose() 释放对象weak→0 → destroy() 删除控制块这套机制正是 Day05 weak_ptr 的前置地基。
默认控制块保存真实类型指针 Y*,并在 dispose() 里 delete:
template<typename Y>
class sp_counted_impl_p : public sp_counted_base {
Y* px_;
void dispose() noexcept override {
delete px_; // delete 的是 Y*(真实类型)
}
};
shared_count 的职责就是“持有 sp_counted_base* 并做引用计数增减”:
add_ref_copy()(use_count++)release()(use_count--,归零触发 dispose / destroy 链路)use_count():供 shared_ptr 转发观察Day02 的 shared_ptr 最明显变化:
T* ptr_ + long* count_T* ptr_ + shared_count pn_shared_ptr 不再直接碰 use_count_,只负责:
get / operator* / operator-> / operator boolshared_count问题:shared_ptr<Base> 如何正确释放 new Derived?
Day02 的关键点是:释放看真实类型 Y,而不是表面类型 T。
T 决定访问视角:shared_ptr 里存 T*Y 决定释放方式:控制块是 sp_counted_impl_p<Y>,dispose() 里 delete (Y*)因此构造函数写成模板:
template<typename Y>
explicit shared_ptr(Y* p)
: ptr_(p), pn_(p) { // pn_(p) 内部 new sp_counted_impl_p<Y>(p)
}
这样 shared_ptr<Animal> animal(new Dog) 时:
AnimalDog*sp_counted_impl_p<Dog>::dispose() → delete Dog*这就是“类型擦除”的本质:
shared_ptr 本身只保留 sp_counted_base*(类型被擦掉),真实类型信息被封装进控制块派生类里。
我用了一个动物类层次结构(Animal / Dog / Cat),以及 5 组测试:
dog1:use_count=1dog2(dog1):两者 use_count=2shared_ptr<Animal> animal(new Dog):能 speak,多态输出正确shared_ptr<Animal> pets[3] 混合存 Dog/Catanimal = dog 后两者 use_count=2p1.reset() 后:p1 为空,p2 仍持有,计数正确p2.reset(new Dog):旧对象释放,新对象接管成功运行输出(控制台)与每个测试的断言预期一致,验证了“控制块 + shared_count + shared_ptr 变薄”的闭环正确。
因为需要把生命周期拆成两段:
use_count 决定(最后一个 shared_ptr 释放对象)weak_count 决定(最后一个 weak_ptr 才能删控制块)没有这两个阶段,Day05 引入 weak_ptr 会非常别扭。
因为控制块需要一份“隐含弱引用”来表示:只要 use_count>0,控制块必须存在。
最后一个 shared_ptr 释放对象后,会同时释放这份隐含弱引用:
shared_ptr 不再直接存 “真实类型/删除策略”,只存一个 sp_counted_base*。
真实类型 Y(以及删除方式)被封装在派生控制块 sp_counted_impl_p<Y> 的 dispose() 里,通过虚函数分发回正确实现。
因为我们需要支持:
shared_ptr<Dog> dog(new Dog);
shared_ptr<Animal> animal = dog; // 合法
它的语义是:两者共享同一控制块(计数+1),但对外访问指针从 Dog* 视角变成 Animal* 视角。
sp_counted_base → sp_counted_impl_p<Y>(默认 delete)→(Day03)sp_counted_impl_pd<Y,D>(自定义 deleter)。sp_counted_base*,真实类型与删除方式藏在控制块派生类里,析构通过虚函数分发到正确 delete。Day02 已完成:控制块分离架构(Boost 风格骨架)
sp_counted_base:强/弱计数 + dispose/destroy 两段式释放sp_counted_impl_p<Y>:默认 delete 的具体控制块shared_count:控制块 RAII 管理器shared_ptr<T> 变薄:T* + shared_count