std::weak_ptr 要与 std::shared_ptr一起搭配使用。

一个std::weak_ptr对象看做是std::shared_ptr对象管理的资源的观察者,它不影响共享资源的生命周期

  • 如果需要操作weak_ptr正在观察的资源,可以将weak_ptr提升为shared_ptr。
  • 当shared_ptr管理的资源被释放时,weak_ptr会自动变成 nullptr。

当weak_ptr类型指针的指向和某一shared_ptr指针相同时,weak_ptr指针并不会使shared_ptr所指控制块的引用计数加1;同样,当weak_ptr指针被释放时,shared_ptr所指控制块的引用计数也不会因此而减1。也就是说,weak_ptr类型指针并不会影响shared_ptr所指控制块的引用计数

weak_ptr的作用

weak_ptr主要为了解决shared_ptr的循环引用垂悬指针问题:

循环引用问题

循环引用是指两个或多个对象相互持有对方的shared_ptr,从而导致引用计数永远不为零,造成内存泄漏。

class B;

class A {
public:
    std::shared_ptr<B> b;
};

class B {
public:
    std::shared_ptr<A> a;
};

int main() {
    auto spa = std::make_shared<A>(); // spa引用计数为1
    auto spb = std::make_shared<B>(); // spb引用计数为1
    spa->b = spb; // spb引用计数为2
    spb->a = spa; // spa引用计数为2
}

当离开main()函数作用域时,spa和spb被销毁,引用计数各减一,没有降为零,所以A和B都不会被销毁,造成内存泄漏。

weak_ptr的解决方案是:将其中一个shared_ptr替换为weak_ptr。

class B;

class A {
public:
    std::shared_ptr<B> b;
};

class B {
public:
    std::weak_ptr<A> a;  // 使用weak_ptr代替shared_ptr
};


int main() {
    auto spa = std::make_shared<A>(); // spa引用计数为1
    auto spb = std::make_shared<B>(); // spb引用计数为1

    spa->b = spb; // spb引用计数为2
    spb->a = spa; // spa引用计数还是为1



    return 0;
}

当离开main()函数作用域时,

  • spa和spb被销毁,引用计数各减1。
  • spa的引用计数从1降到0,A对象被销毁。
  • A析构时spb的引用计数从1降到0,B对象被销毁。

空悬指针问题

有两个共享指针sp1和sp2,指向堆上的同一个对象,sp1和sp2位于不同的线程中。假设线程A通过sp1指针将对象销毁了(尽管把sp1置为了NULL),那p2就成了空悬指针。

weak_ptr不控制对象的生命期,但是它知道对象是否还活着。如果对象还活着,那么它可以提升为有效的shared_ptr(提升操作通过lock()函数获取所管理对象的强引用指针);如果对象已经死了,提升会失败,返回一个空的shared_ptr。

实现原理

weak_ptr的实现依赖于shared_ptr的控制块。

shared_ptr在内部不仅包含一个指向对象的裸指针,还包含一个指向控制块的指针,它通常包含:

  • 强引用计数:由shared_ptr维护,表示有多少个shared_ptr指向该对象。当强引用计数降为0时,对象本身会被销毁。
  • 弱引用计数:由weak_ptr维护,表示有多少个weak_ptr指向该对象。当强引用计数和弱引用计数都为零时,控制块本身才会被销毁。

同时weak_ptr在内部也存储一个指向控制块的指针,但它不增加强引用计数。

weak_ptr的主要作用是观察这个控制块。通过检查控制块中的强引用计数是否为0,它可以判断所指向的对象是否仍然存活。

weak_ptr的核心方法是lock()lock()方法会返回一个shared_ptr:

  • 如果对象仍然存活(强引用计数大于0),lock()会返回一个有效的shared_ptr,并安全地增加强引用计数。
  • 如果对象已被销毁(强引用计数为 0),lock()会返回一个空的shared_ptr。

初始化

weak_ptr必须从一个已经存在的shared_ptr或另一个weak_ptr来初始化:

  • 通过shared_ptr直接初始化
  • 可以通过隐式转换来构造
  • 支持拷贝语义
  • 支持移动语义
int main()
{
    std::shared_ptr<Foo> f(new Foo());

    std::weak_ptr<Foo> f1(f);                     // shared_ptr直接构造
    std::weak_ptr<Foo> f2 = f;                    // shared_ptr隐式转换为weak_ptr

    std::weak_ptr<Foo> f3(f1);                    // 拷贝构造函数
    std::weak_ptr<Foo> f4 = f1;                   // 拷贝构造函数
    std::weak_ptr<Foo> f5(std::move(f2));         // 移动构造函数

    std::weak_ptr<Foo> f5;                        // 默认构造,无法通过lock()方法获取到shared_ptr
}

成员函数

函数 描述
expired() 检查 weak_ptr 所指向的对象是否已被销毁。如果对象已经不存在(即强引用计数为零),则返回 true
lock() 核心函数。如果 weak_ptr 所指向的对象仍然存在,它将返回一个有效的 shared_ptr,并增加对象的强引用计数。如果对象已被销毁,则返回一个空的 shared_ptr
reset() weak_ptr 置为空,使其不再指向任何对象。
use_count() 返回当前管理该对象的 shared_ptr 实例的数量。注意:它返回的是强引用计数