1. shared_ptr 的真实结构

一个 std::shared_ptr<T> 并不是只有一个指针,内部通常包含两个指针:

template<class T>
class shared_ptr {
    T* ptr;              // 指向对象
    control_block* cb;   // 控制块
};

控制块 control_block 负责:

struct control_block {
    long shared_count;   // shared_ptr 数量
    long weak_count;     // weak_ptr 数量
    void (*deleter)(T*); // 删除函数
    T* object;           // 对象指针
};

内存结构大概是:

shared_ptr
   |
   v
+-----------+       +----------------------+
| ptr ----+ | ----> | object (T)           |
| cb -----|-|----+  +----------------------+
+-----------+    |
                 v
           +----------------------+
           | control block        |
           | shared_count = 1     |
           | weak_count   = 0     |
           | deleter              |
           +----------------------+

2. 引用计数工作方式

当你复制 shared_ptr

auto p1 = std::make_shared<int>(10);
auto p2 = p1;

发生:shared_count = 2

p1 析构:shared_count = 1

p2 析构:shared_count = 0

此时:delete object

control block 不一定删除,因为可能还有 weak_ptr

3. weak_ptr 的作用

weak_ptr 不会增加 shared_count,只增加:weak_count

结构:

shared_count = 0
weak_count   = 1

当:shared_count == 0

对象删除:delete object

当:weak_count == 0

控制块删除:delete control_block

流程:

shared_count -> 0
    |
    v
delete object

weak_count -> 0
    |
    v
delete control_block

4. 为什么 make_shared 更高效

面试经典问题。看两种写法:

写法 1(不推荐)

std::shared_ptr<int> p(new int(10));

内存分配:

1 malloc -> object
1 malloc -> control block

内存布局:

+---------+        +----------------+
| object  |        | control block  |
+---------+        +----------------+

需要 两次 malloc

写法 2(推荐)

auto p = std::make_shared<int>(10);

实现方式:

1 malloc

同时分配:

object + control_block

内存布局:

+-------------------------------+
| control block | object (T)    |
+-------------------------------+

只需要:

1 malloc

优势:

  1. 减少一次内存分配:malloc 是昂贵操作
  2. 更好的 cache locality:内存连续:CPU cache hit ↑
  3. 更安全(异常安全)

看这个代码:

foo(std::shared_ptr<int>(new int(10)), bar());

可能发生:

new int(10)
bar() throws

导致:内存泄漏

foo(std::make_shared<int>(10), bar());

不会。

5. make_shared 的缺点

虽然推荐,但有一个隐藏问题

如果:shared_ptr 全部释放,但 weak_ptr 还存在

情况:shared_count = 0, weak_count > 0

对象会释放吗?

  • 如果是 new, delete object control block 还在
  • 如果是 make_shared,因为object 和 control block 在同一块内存,结果整块内存不能释放,所以object 内存仍然占用,这叫weak_ptr 延迟释放问题

6. 为什么 shared_ptrunique_ptr

shared_ptr 的代价:

  1. 引用计数是原子操作,多线程安全,代价:CPU fence,cache line sync
  2. 控制块需要额外内存
  3. 间接访问
shared_ptr -> control block -> object

所以 C++ 社区规则:优先 unique_ptr,必要才 shared_ptr

7. LLVM 为什么几乎不用 shared_ptr

LLVM 基本规则:

unique_ptr + raw pointer

原因:

  1. 编译器 IR 是树结构,天然:single ownership
  2. shared_ptr 开销太大,LLVM 追求零开销抽象
  3. 生命周期由 Arena / Context 管理

为什么推荐 make_shared

标准回答:

  1. 减少一次内存分配
  2. object 和 control block 内存连续
  3. 更好的 cache locality
  4. 更好的异常安全

但也要补一句:

如果 weak_ptr 生命周期很长
make_shared 可能导致 object 内存延迟释放