文章目录

  • 概述
  • 构建
  • expired函数
  • 获取指针
  • cyclic reference例子


概述

shared_ptr的作用主要是在最后一个指向资源的shared_ptr销毁时自动释放资源,然而在某些场景下这种行为可能不被期望。例如:

  • 两个或者多个对象都使用shared_ptr,并且相互通过shared_ptr指向对方,如果存在一个环路(cyclic reference),那么由于环路上的shared_ptr的use_count最终无法降为0,所以环路上的资源将无法被释放。
  • 当我们想使用“希望共享但不拥有”某个对象的语义的情况下,shared_ptr也无法提供此语义。

因此weak_ptr可以在这两种场景下使用。

构建

weak_ptr可以通过shared_ptr构建以及赋值

shared_ptr<ClassA> spA(new ClassA);
weak_ptr<ClassA> wpA1(spA);
weak_ptr<ClassA> wpA2 = spA;

expired函数

weak_ptr并不增加shared_ptr的引用计数,因此有可能发生对象已经析构但是weak_ptr还在的情况,此时使用weak_ptr就必须小心,当对象即将发生析构或者已经析构,expired返回true,expired函数效率比use_count高,该函数返回true时其结果才有意义,因为返回false时,此时执行下一个语句,对象有可能在其他线程同时被释放。

获取指针

使用weak_ptr时访问对象的方式有所变化:

Alice->m_otherOne->m_name;  // shared_ptr访问对象方式
Alice->m_otherOne.lock()->m_name;  // weak_ptr访问对象方式

lock函数返回一个从weak_ptr构建的临时的shared_ptr,通过此shared_ptr我们可以访问对象。lock函数的调用含义相当于以下语句:

expired() ? shared_ptr<T>() : shared_ptr<T>(*this)

lock函数的调用是原子操作,是线程安全的,但上述等效写法不是原子操作

cyclic reference例子

#include <iostream>
#include <memory>
using namespace std;

class Person
{
public:
    Person(const string& name)
    {
        m_name = name;
    }
    ~Person()
    {
        cout << "destroy: " << m_name << endl;
    }
    void meetSomeone(shared_ptr<Person> someone)
    {
        m_otherOne = someone;
    }
public:
    string m_name;
    std::shared_ptr<Person> m_otherOne;
};

int main()
{
    shared_ptr<Person> Alice(new Person("Alice"));
    shared_ptr<Person> Bob(new Person("Bob"));
    cout << "Alice use count: " << Alice.use_count() << endl;
    cout << "Bob use count: " << Bob.use_count() << endl;
    Alice->meetSomeone(Bob); // --> 1
    Bob->meetSomeone(Alice); // --> 2
    cout << "Alice use count: " << Alice.use_count() << endl;
    cout << "Bob use count: " << Bob.use_count() << endl;
    return 0;
}

程序输出

Alice use count: 1
Bob use count: 1
Alice use count: 2
Bob use count: 2

对Alice Person而言,共有两个shared_ptr指向它,一个是作用域内的Alice shared_ptr,一个是Bob Person中的m_otherOne;Bob Person同理。
可以看到最终对象并没有被释放,这是因为main结束时,先析构Bob shared_ptr(声明逆序析构),此时Bob shared_ptr的use_count为2,析构函数内use_count减为1,因此不会释放Bob Person对象。接着释放Alice shared_ptr,与前面同理不会释放Alice Person对象。

尝试注释第1处,输出变为:

Alice use count: 1
Bob use count: 1
Alice use count: 2
Bob use count: 1
destroy: Bob
destroy: Alice

读者可以按照上面的思路自己解释一下这个结果。

为了避免上述这样的循环引用(cyclic reference)我们可以进行如下改动:

std::weak_ptr<Person> m_otherOne;  // weak_ptr!!

此时输出结果为:

Alice use count: 1
Bob use count: 1
Alice use count: 1
Bob use count: 1
destroy: Bob
destroy: Alice

同样可以避免资源未释放的问题

参考:

  1. https://stackoverflow.com/questions/41500557/how-weak-ptr-and-shared-ptr-accesses-are-atomic
  2. https://en.cppreference.com/w/cpp/memory/weak_ptr/expired