智能指针之weak_ptr
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实例的数量。注意:它返回的是强引用计数 |