你说的weak_ptr只持有控制块的指针,不持有对象指针,我不太明白,如果真的不持有对象指针,那么lock()函数如何能返回对象指针呢?
好问题,你抓住了我之前说法不准确的地方。让我纠正并说清楚。
我之前的说法有误,更正如下
weak_ptr 实际上同时持有两个指针,和 shared_ptr 的内部结构几乎一样:
cpp
// libstdc++ 的实际实现(简化)
template<typename T>
class weak_ptr {
T* _M_ptr; // ← 指向对象的指针(也有!)
__weak_count _M_refcount; // ← 指向控制块的指针
};
template<typename T>
class shared_ptr {
T* _M_ptr; // ← 同样的对象指针
__shared_count _M_refcount; // ← 同样指向控制块
};
两者的区别不是”有没有对象指针”,而是:
shared_ptr构造时递增use_count(强引用计数)weak_ptr构造时只递增weak_count(弱引用计数),不影响use_count
lock() 的真实过程
有了这个基础,lock() 的逻辑就很清晰了:
cpp
shared_ptr<T> weak_ptr<T>::lock() const noexcept {
// 第一步:去控制块里检查 use_count
if (use_count == 0) {
return shared_ptr<T>(); // 对象已死,返回空
}
// 第二步:CAS 原子地把 use_count 加 1
// 成功后 use_count >= 1,对象此刻一定存活
// 第三步:用 weak_ptr 自己存的 _M_ptr 构造 shared_ptr
return shared_ptr<T>(_M_ptr, control_block);
// ^^^^^^
// 直接用 weak_ptr 里存的对象指针
}
_M_ptr 一直在那里,lock() 只是在确认对象还活着之后,把它包进一个新的 shared_ptr 返回出去。
那个对象指针不会是悬空指针吗?
这是你问题的核心。答案是:技术上可能是悬空的,但永远不会被解引用。
时间线:
t=0 shared_ptr sp = make_shared<Player>();
weak_ptr wp = sp;
状态:wp._M_ptr → [Player对象],use_count=1
t=1 sp.reset(); // 最后一个 shared_ptr 销毁
→ use_count 降为 0
→ Player 析构函数被调用
→ (new 分配的情况下)Player 的内存被释放
状态:wp._M_ptr 现在是悬空指针!但 wp 没有人去解引用它
t=2 wp.lock() 被调用:
→ 检查 use_count == 0
→ 返回空 shared_ptr,_M_ptr 根本没有被碰
weak_ptr 存着那个可能悬空的指针,但它从来不直接解引用它。这个指针唯一的用途,是在 lock() 确认 use_count > 0 之后,被打包进新的 shared_ptr——而此时对象一定还活着,指针一定有效。
用 make_shared 时甚至更安全——对象和控制块在同一块内存,use_count=0 时只调用析构函数,内存还没释放,所以指针严格来说都不是悬空的,只是指向一个”已析构但内存还在”的位置。
修正后的完整内存图
shared_ptr 和 weak_ptr 同时存在:
shared_ptr: [ _M_ptr ────────────────────┐
_M_refcount → ControlBlock ]│
│
weak_ptr: [ _M_ptr ────────────────────┘ (同一个地址)
_M_refcount → ControlBlock ] (同一个控制块)
┌──────────────────────────┐
ControlBlock: │ use_count = 1 │
│ weak_count = 2 │
└──────────────────────────┘
shared_ptr 销毁后(use_count=0):
weak_ptr 仍然持有 _M_ptr(可能悬空)和控制块指针(一定有效)
lock() 先看控制块 → use_count=0 → 直接返回 null
_M_ptr 从未被碰
所以一句话总结:weak_ptr 确实存着对象指针,lock() 正是用它来构造返回的 shared_ptr。控制块的 use_count 是判断对象是否存活的唯一依据,只有它确认对象活着,对象指针才会被使用。
