i007.cc

i007.cc

优先队列-降维打击

05.价值资料

你说的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 是判断对象是否存活的唯一依据,只有它确认对象活着,对象指针才会被使用。

发表回复