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
优势:
- 减少一次内存分配:malloc 是昂贵操作
- 更好的 cache locality:内存连续:
CPU cache hit ↑ - 更安全(异常安全)
看这个代码:
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 objectcontrol block还在 - 如果是
make_shared,因为object 和 control block 在同一块内存,结果整块内存不能释放,所以object 内存仍然占用,这叫weak_ptr 延迟释放问题
6. 为什么 shared_ptr 比 unique_ptr 慢
shared_ptr 的代价:
- 引用计数是原子操作,多线程安全,代价:CPU fence,cache line sync
- 控制块需要额外内存
- 间接访问
shared_ptr -> control block -> object
所以 C++ 社区规则:优先 unique_ptr,必要才 shared_ptr
7. LLVM 为什么几乎不用 shared_ptr
LLVM 基本规则:
unique_ptr + raw pointer
原因:
- 编译器 IR 是树结构,天然:single ownership
shared_ptr开销太大,LLVM 追求零开销抽象- 生命周期由 Arena / Context 管理
为什么推荐 make_shared?
标准回答:
- 减少一次内存分配
- object 和 control block 内存连续
- 更好的 cache locality
- 更好的异常安全
但也要补一句:
如果 weak_ptr 生命周期很长
make_shared 可能导致 object 内存延迟释放