为什么设计shared_ptr时要另写一个ControlBlock结构体

懒得总结了,直接看链接吧⬇️
关于shared_ptr的设计

算了还是copy一下吧


为什么 std::shared_ptr 不把引用计数(ref_count)直接放在对象内部,而是必须放在一个“控制块”(control block)结构体里?

原因 1:对象本身不一定能被修改(可能没有权限添加 ref_count)

你用 shared_ptr 管理的对象,可能是:

  • 第三方库的对象
  • C 库对象
  • 不可修改源码的对象
  • 内置数组
  • 动态分配的基本类型

例如:

1
2
3
shared_ptr<int> p(new int(10));
shared_ptr<MyType> p(new MyType);
shared_ptr<FILE> p(fopen(...), fclose);

你不可能往 intFILE 里塞一个 ref_count 成员变量。

shared_ptr 必须支持任意类型的对象,因此引用计数不能放在对象内部。


原因 2:多个 shared_ptr 可以指向同一个对象(不共享对象本身的成员)

重要事实:

1
2
shared_ptr<T> p1(new T);
shared_ptr<T> p2 = p1;

p1p2 共享同一个对象,但 它们各自是独立的智能指针对象

如果 ref_count 写在对象里:

1
2
3
struct T {
int ref_count; // 多个 shared_ptr 如何管理?
}

那两个 shared_ptr 怎么找到同一个 ref_count?
它们属于不同的 shared_ptr,但对象只有一个。

另外:

1
2
shared_ptr<T> p3;
p3 = p1;

赋值、移动、替换对象都会导致复杂的同步问题。

引用计数必须在一个能够被多个 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
2
3
 ---------------------------
| control block | object T |
---------------------------

内存打包更紧凑、更高效、减少 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
2
3
4
5
shared_ptr control block:
shared_count
weak_count
deleter
pointer to object

weak_ptr 依赖 control block,如果把 ref_count 放对象里,weak_ptr 根本无法实现。


总结一句:为什么不用对象成员 ref_count?

因为 shared_ptr 必须支持

  • 任意类型(甚至不支持修改的外部类型)
  • 多个 shared_ptr 共享计数
  • 自定义删除器
  • make_shared 优化
  • weak_ptr 弱引用系统
  • 分配器定制

这些能力都要求:

引用计数必须放在一个独立的控制块(control block)中,而不是对象内部。

这是 shared_ptr 设计的核心。