C++ 为什么要使用weak_ptr及其使用场景

我们知道std::shared_ptr会共享对象的所有权,但是有一些场景如果有一个像std::shared_ptr但是又不参与资源所有权共享的指针是很方便的。换句话说,是一个类似std::shared_ptr但不影响对象引用计数的指针。不参与资源所有权就意味着不会对资源的生命周期产生影响,有利于对象之间的解耦。举个一个不太恰当的例子,A和B相互加了微信,假设我们用一个指针来指向自己的微信朋友,如果是shared_ptr,那么A和B的生命周期是相互影响的,而实际上我们并不希望这种强绑定,比如假设B注销了账户,A根本不用知道,只有当A想发消息给B的时候系统才会发出提示:您还不是该用户的朋友。这时候weak_ptr就派上用场了。这也就是weak_ptr的第一种使用场景:

当你想使用对象,但是并不想管理对象,并且在需要使用对象时可以判断对象是否还存在

这种模式就是观察者模式:

  1. 观察者模式是在subject状态发生改变时,通知观察者的一种设计模式。

  2. 在多数实现中,每个subject持有指向观察者的指针,这使得当subject状态改变时可以很容易通知观察者。

  3. subject不会控制其观察者的生存期,因此应该是持有观察者的weak_ptr指针。同时在subject的使用某个指针时,可以先确定是否空悬。
    比如当某个微信用户需要群发消息给所有的好友(这里假设观察者是微信好像),实际上假如对方已经删除了好友这条消息是发布出去,weak_ptr相对于是提供了一个种方法来判断对方是否已经删好友了的功能,如果没删就发消息;删了就不理会。
    第二种场景:解决循环引用
    C++ 为什么要使用weak_ptr及其使用场景_第1张图片

  4. A、B、C三个对象的数据结构中,A和C共享B的所有权,因此各持有一个指向B的std::shared_ptr;

2. 假设有一个指针从B指回A(即上图中的红色箭头),则该指针的类型应为weak_ptr,而不能是裸指针或shared_ptr,原因如下:

①假如是裸指针,当A被析构时,由于C仍指向B,所以B会被保留。但B中保存着指向A的空悬指针(野指针),而B却检测不出来,但解引用该指针时会产生未定义行为。

②假如是shared_ptr时。由于A和B相互保存着指向对方的shared_ptr,此时会形成循环引用,从而阻止了A和B的析构。

③假如是weak_ptr,这可以避免循环引用。假设A被析构,那么B的回指指针会空悬,但B可以检测到这一点,同时由于该指针是weak_ptr,不会影响A的强引用计数,因此当shared_ptr不再指向A时,不会阻止A的析构。
 还是以微信好友为例,删好友是不用对方同意,如果用shared_ptr就意味着我删不了你,你也删不了我。示例代码如下:

class B; // 前置声明类B
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	weak_ptr<B> _ptrb; // 指向B对象的弱智能指针。引用对象时,用弱智能指针
};
class B
{
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
	weak_ptr<A> _ptra; // 指向A对象的弱智能指针。引用对象时,用弱智能指针
};
int main()
{
    // 定义对象时,用强智能指针
	shared_ptr<A> ptra(new A());// ptra指向A对象,A的引用计数为1
	shared_ptr<B> ptrb(new B());// ptrb指向B对象,B的引用计数为1
	
    // A对象的成员变量_ptrb也指向B对象,B的引用计数为1,因为是弱智能指针,引用计数没有改变
	ptra->_ptrb = ptrb;
	// B对象的成员变量_ptra也指向A对象,A的引用计数为1,因为是弱智能指针,引用计数没有改变
	ptrb->_ptra = ptra;

	cout << ptra.use_count() << endl; // 打印结果:1
	cout << ptrb.use_count() << endl; // 打印结果:1

	/*
	出main函数作用域,ptra和ptrb两个局部对象析构,分别给A对象和
	B对象的引用计数从1减到0,达到释放A和B的条件,因此new出来的A和B对象

第三种:缓存对象

1. 考虑一个工厂函数loadWidget,该函数基于唯一ID来创建一些指向只读对象的智能指针。

2. 假设该只读对象需要被频繁使用,而且经常需要从文件或数据库中加载。那么可以考虑将对象缓存起来。同时为了避免过量缓存,当不再使用时,则将该对象删除。

3. 由于带缓存,工厂函数返回unique_ptr类型显然不合适。因为调用者和缓存管理器均需要一个指向这些对象的指针。

4. 当用户用完工厂函数返回的对象后,该对象会被析构,此时相应的缓存条目将会空悬。因为可以考虑将工厂函数的返回值设定为shared_ptr类型,而缓存类型为weak_ptr类型。

你可能感兴趣的:(C++,c++,开发语言,后端)