一、循环引用崩溃现场
struct Node {
shared_ptr<Node> next;
};
auto a = make_shared<Node>();
auto b = make_shared<Node>();
a->next = b;
b->next = a; // 💥 循环引用!
a.ptr → [Node A | next→b的control_block] ref_count_A = 2 (a + b->next)
b.ptr → [Node B | next→a的control_block] ref_count_B = 2 (b + a->next)
a.reset() → ref_count_A = 1 (还被 b->next 持有) → 不释放
b.reset() → ref_count_B = 1 (还被 a->next 持有) → 不释放
→ 两个节点永远不释放!
二、用 weak_ptr 破解
struct Node {
weak_ptr<Node> next; // 不增加引用计数!
};
a->next = b; // weak_ptr = b → weak_count++,ref_count 不变
b->next = a; // 同上
a.reset() → ref_count_A = 0 → 释放 A → b->next 变成空悬
b.reset() → ref_count_B = 0 → 释放 B
✅ 完美释放!
三、weak_ptr.lock() 的底层安全检查
shared_ptr<Node> sp = weak_node.lock(); // 安全提升
if (sp) {
// 此时对象一定活着,sp 持有引用计数
}
底层做了什么:
- 原子读
control_block.ref_count,如果不为 0。 - 原子加
ref_count.fetch_add(1)(lock xadd)。 - 返回
shared_ptr(指向对象)。 - 如果
ref_count == 0(对象已释放),返回空shared_ptr。
为什么不直接用 weak_ptr 转 shared_ptr 的拷贝构造?
- 因为需要检查+增加两个操作的原子性——
lock()内部用 CAS 循环保证不会被并发释放打断。