shared_ptr和weak_ptr深入解析

我写的是参考vs2013中的std源码写的,boost的不要看了,c++11出来都好久了。

这些东西必须记录下来,不然过了3天就忘了。

要使用shared_ptr, weak_ptr,需要#include <memory>
这2种指针都派生于_Ptr_base
_Ptr_base类有2个成员变量
_Ty *_Ptr;
_Ref_count_base *_Rep;
分别是 指向客户对象的指针 和 指向引用计数对象的指针,引用计数对象是初始化智能指针时建立的,有强引用计数和弱引用计数,强弱引用的概念具体看下面说明
这2个指针在智能指针没有指定任何客户对象时都被_Ptr_base初始化成0
_Ref_count_base是一个虚基类,这个虚基类的指针实际指向的实现类_Ref_count或_Ref_count_del等等这些类的对象
就拿_Ref_count为代表来说,_Ref_count实现了虚基类_Ref_count_base的虚函数
virtual void _Destroy()
virtual void _Delete_this()
_Destroy()用于删除智能指针指向的客户对象(强引用计数为0时删除),_Delete_this()用于删除引用计数对象(就是初始化建立的_Ref_count对象,弱引用计数为0时删除)

shared_ptr
就叫它共享指针,它存在的意义是根据客户对象的强引用计数管理客户对象生存期,强引用计数为0了就删除客户对象
单独构造shared_ptr的时候建立一个_Ref_count对象,并且强引用和弱引用都是1,如果客户类派生于enable_shared_from_this<客户类>,还需要对enable_shared_from_this<客户类>的weak_ptr类成员_Wptr初始化成当前shared_ptr的客户对象和计数对象,并保持当前的强引用计数不变,弱引用计数加1,就是使客户对象自身的weak_ptr被初始化成客户对象和客户的计数对象,相当于客户对象多了一个弱引用,还共享引用计数(引用计数对象只能有一个,必须共享)。
析构的时候,客户对象的强引用计数减1,(如果强引用计数为0,(先删除客户对象,再将弱引用计数减1,(如果弱引用计数为0,再删除_Ref_count这个计数对象))),注意这个顺序是关键,我都打起括号了。如果客户对象派生于enable_shared_from_this<客户类>,前面说过弱引用计数还多1呢,怎么减少这个计数呢?因为enable_shared_from_this<客户类>是客户类的父类,所以最后要析构它,而这个父类的成员就一个就是弱引用计数对象,析构这个成员的时候自然减1了,这个是最后的减1,计数对象强弱引用都为0,被删掉了。总结下弱引用计数什么时候减1:在强引用为0时,在客户对象的父类enable_shared_from_this<客户类>析构的时候。
拷贝构造的时候将先将右侧客户对象的强引用计数加1,再将左侧当前客户对象的强引用计数减1(并判断是否析构,需要析构时做析构处理),注意顺序,如果先左侧减1再右侧加1,就可能导致同一个对象先删除再增加计数,引起空指针错误。
赋值运算符重载的时候,建立一个新的临时shared_ptr对象,调用右侧shared_ptr对象的拷贝构造函数,使右侧shared_ptr对象指向的客户对象的强引用计数加1,然后自己跟这个临时对象进行交换(交换客户对象指针和客户对象引用对象,这里其实是右值拷贝,就是内存换马甲)。最后临时对象销毁时再使左侧的客户对象强引用计数减1,并做析构判断处理
可以看出shared_ptr的拷贝构造和赋值是等效的。

weak_ptr
弱指针存在的意义在于,在使用客户对象的时候,如果客户对象已经销毁了依然可以引用客户对象而不至于崩溃。弱引用指针在使用客户对象时需要转成shared_ptr,如果客户对象删除了,会提示失败
弱引用计数可以理解为_Ref_count这些计数对象的强引用,弱引用计数为0,就删除计数对象
析构的时候只减少弱引用计数,如果弱引用计数为0,就是删除计数对象。
弱引用拷贝构造和赋值,只对新客户对象的弱引用计数加1,并对旧客户对象的弱引用计数减1,最后做析构判断处理。

客户对象派生于enable_shared_from_this<客户类>的,会带成员函数shared_from_this(),这个只是通过客户对象派生的父类中的weak_ptr类成员拷贝构建一个新的shared_ptr智能指针,所以强引用计数会加1,弱引用计数不变,有一点要注意通过weak_ptr对象拷贝构造的shared_ptr智能指针,只在weak_ptr对象对应的客户对象的强引用计数大于0时才能引用到客户对象,不然对应的客户指针和客户计数对象都是0,因为使用了_Incref_nz()来增加强引用计数。

要用智能指针就要保证客户对象不再被普通指针操作,不然会引起混乱。
被智能指针管理的对象最好派生于enable_shared_from_this<客户类>,最好第一个派生于它,这样能最后被析构,这样在客户类内部就可以找到自己的计数对象,并用shared_from_this()增加自己的强引用,来确保自己不会删除掉,并最好赋值给一个shared_ptr的局部变量,这样能自动释放强引用。但是不要在客户对象的构造函数和析构函数中使用shared_from_this(),因为在shared_ptr中客户对象先建立,计数对象后建立,析构的时候计数对象先析构,客户对象后析构。你在客户对象的构造函数或析构函数中使用shared_from_this(),是找不到计数对象的,要么是没有建立计数对象,要么是已经析构了计数对象。这个没办法,C++的继承机制决定的。能不能调换下位置?我觉得不能,因为客户对象没初始化好的时候,计数对象就初始化好了,那就代表可以弱引用了,然后一用发现没初始化,那不出错了,析构的时候也是先析构计数对象,原因一样。

上面这些东西都是原理,原理都是相通的,boost里的也是这样,Qt里QSharedPointer,QWeakPointer的也是这样,Android里sp,wp的也是这样。

尽量写的明白吧?欢迎留言指正。

posted on 2019-08-20 14:33  litandy  阅读(1802)  评论(1编辑  收藏  举报

导航