文章目录
- 概述
- 构建
- 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
同样可以避免资源未释放的问题
参考:
- https://stackoverflow.com/questions/41500557/how-weak-ptr-and-shared-ptr-accesses-are-atomic
- https://en.cppreference.com/w/cpp/memory/weak_ptr/expired