为什么设计shared_ptr时要另写一个ControlBlock结构体
懒得总结了,直接看链接吧⬇️
关于shared_ptr的设计
算了还是copy一下吧
为什么 std::shared_ptr 不把引用计数(ref_count)直接放在对象内部,而是必须放在一个“控制块”(control block)结构体里?
原因 1:对象本身不一定能被修改(可能没有权限添加 ref_count)
你用 shared_ptr 管理的对象,可能是:
- 第三方库的对象
- C 库对象
- 不可修改源码的对象
- 内置数组
- 动态分配的基本类型
例如:
1 | shared_ptr<int> p(new int(10)); |
你不可能往 int 或 FILE 里塞一个 ref_count 成员变量。
shared_ptr 必须支持任意类型的对象,因此引用计数不能放在对象内部。
原因 2:多个 shared_ptr 可以指向同一个对象(不共享对象本身的成员)
重要事实:
1 | shared_ptr<T> p1(new T); |
p1 和 p2 共享同一个对象,但 它们各自是独立的智能指针对象。
如果 ref_count 写在对象里:
1 | struct T { |
那两个 shared_ptr 怎么找到同一个 ref_count?
它们属于不同的 shared_ptr,但对象只有一个。
另外:
1 | shared_ptr<T> p3; |
赋值、移动、替换对象都会导致复杂的同步问题。
引用计数必须在一个能够被多个 shared_ptr 共同指向的地方,而不是在对象内部。
原因 3:shared_ptr 必须支持自定义删除器(deleter)
C++ 允许:
1 | shared_ptr<FILE> p(f, fclose); |
每个 shared_ptr 对象有自己的删除器。
删除器如何存储?
如果 ref_count 在对象里,那删除器在哪里?
对象本身不能存储 shared_ptr 的删除器,因为:
- 对象不一定是你能修改的
- 两个 shared_ptr 指向同一个对象可能有不同 deleter(虽然标准里规定共享控制块时 deleter 也共享,但一开始创建时可能不同)
因此:
shared_ptr 需要一个独立的“控制块”来同时存储:
- 引用计数(shared)
- 弱引用计数
- 删除器
- 分配器信息
这些都不能放进对象本身。
原因 4:要支持 make_shared(对象和计数块连续分配)
经典优化:
1 | auto p = make_shared<T>(args...); |
make_shared 一次分配两个东西:对象 + 控制块
如果 ref_count 在对象内部,则:
- 编译器无法保证 ref_count 和对象连续
- make_shared 性能优化就做不到
使用控制块后:
1 | --------------------------- |
内存打包更紧凑、更高效、减少 malloc 次数。
为性能,ref_count 必须独立。
原因 5:weak_ptr 需要弱引用计数,而 weak_ptr 不影响对象本身
weak_ptr 的要求:
1 | weak_ptr<T> w(p); // 不增加 shared count |
如果 ref_count 在对象里,要怎么区分:
- shared_count
- weak_count
对象一开始并不知道弱引用系统的存在。
同时,weak_ptr 需要知道对象是否已被删除,但不能触发 destructor。
因此 C++ 规定:
1 | shared_ptr control block: |
weak_ptr 依赖 control block,如果把 ref_count 放对象里,weak_ptr 根本无法实现。
总结一句:为什么不用对象成员 ref_count?
因为 shared_ptr 必须支持:
- 任意类型(甚至不支持修改的外部类型)
- 多个 shared_ptr 共享计数
- 自定义删除器
- make_shared 优化
- weak_ptr 弱引用系统
- 分配器定制
这些能力都要求:
引用计数必须放在一个独立的控制块(control block)中,而不是对象内部。
这是 shared_ptr 设计的核心。