Home > programming > 强引用智能指针与弱引用智能指针

强引用智能指针与弱引用智能指针

February 25th, 2015 Leave a comment Go to comments

c++代码中最麻烦的就是内存管理了, 还好有了RAII,这样至少可以解放一点我们的脑细胞,不需要时时刻刻注意释放内存或者避免多次释放内存。现在很多人都使用std::shared_ptr 当然了,如果你没有用到C++11,那么可以使用boost::shared_ptr. 不过我们当时使用的不是shared_ptr. 而是学习了ICE以后自己倒腾出来的一个intrusive_ptr,俺们管它叫做Pointer, 非常类似于boost::intrusive_ptr。不过我们当时还不知道boost有这个东西而已(也可能当时还没有这个玩意儿)。

侵入式智能指针和非侵入式智能指针的差别还是挺大的,我们用一幅图来说明一下。 
smart_pointer
这幅图的左边是侵入式智能指针,其引用计数放在了被指向的object的里面,intrusive_ptr指向这个object即可。而右边是非侵入式智能指针,引用计数被单独放在一块区域(其中还包括一个弱引用的计数)。non-intrusive_ptr(shared_ptr, weak_ptr)指向object和引用计数区域。这样一来,对于intrusive_ptr,我们还是可以直接使用raw pointer, 当然这样并不好,也可以再把一个raw pointer传递给一个intrusive_ptr, 其引用计数不会有被中断的情况。而non-intrusive_ptr就不能这样,raw pointer传递给他们以后引用计数只能重新开始计数。还有就是由于侵入式智能指针的引用计数和目标object实例在一块, 那么可以更加有效的利用CPU cache。 而非侵入式的智能指针因为有两个指针,就会稍弱一点。但是呢,侵入式智能指针相比非侵入式智能指针也有一个缺点,这就是我们要讲的弱引用智能指针。 
那么弱引用到底是什么?有什么用处呢? 我们知道智能指针的引入是为了减少内存泄漏的问题,顺带着把野指针,多次释放内存等问题一块解决了。但是有的时候只能指针也会有内存泄漏问题,循环引用–就是这种情况。当两个object互相持有对方的引用,那么他们的引用计数都不会减少到0,那么这两个object也都不会被释放。解决的办法也就是弱引用. 当两个object需要持有对方的时候,其中一方持有对方的弱引用,弱引用是不增加(强)引用计数的–即上图右中的reference count,但是会增加weak reference count. 举个例子,有A,B两个对象实例,其中A持有B的强引用refB, 但是B只持有A的弱引用refA. 当除B以外的object不再持有对A的引用的时候,由于弱引用不会增加引用计数,所以A的引用计数会降至0,此时A会被释放掉,同时B的引用计数也会减少。这样就可以有效的解决循环引用造成的内存泄漏问题。

现在我们来看看弱引用是如何实现的。如上图右边所示,非侵入式智能指针有两个指针。其中一个指向对象实例,我们命名为px; 另外一个指向一块专门用来存放引用计数的区域,我们为之命名为pn. pn所指向的区域( 就叫RefCountZone吧 )里面一共有两个变量,reference count和weak reference count, 他们分别记录强引用和弱引用的次数。当使用shared_ptr的时候,reference count就会加1,而weak reference count不会有变化。而使用weak_ptr的时候, weak reference count就会加1,但是reference count不会有变化。这样一来当所有的强引用都消失了, 那么reference count就会变成0,这个时候就可以删除px所指向的对象实例了。但是值得注意的是在这个时候pn所指向的区域不一定被删除。我们假设这个时候还有弱引用到这个区域,那么pn指向的区域是不会被删除的。这时候如果我们需要使用weak_ptr指向的object 实例的时候,首先需要调用weak_ptr::lock方法,该方法可以获得一个shared_ptr(但可能是空)。weak_ptr::lock首先检查pn->reference count是否为0,如果是0 的话,那么说明px指向的实例已经被删除,那么lock方法会失败,返回一个不指向任何实例的shared_ptr,如果pn->reference count不为0,那么说明px所指向的实例还存在,这个时候可以返回一个该实例的shared_ptr. 当所有的弱引用都消失的时候,也就是pn->weak reference count == 0的时候,同时pn->reference count也是0,那么说明已经没有任何对这个实例的引用了(不论是强引用还是弱引用),这是pn指向的区域就可以被安全删除了。 
从上面我们也可以看出弱引用很巧妙的利用了非侵入式智能指针的RefCountZone。同时利用了reference count和weak reference count来分别控制强引用和弱引用的个数。但是侵入式智能指针就不能很好的实现弱引用了,因为如果我们要实现弱引用,那么就需要一块区域来存放所引用的次数,而且这个块区域不能随着object instance的消亡而消亡,否者当强引用完全消失但还存在弱引用的时候由于object instance已经被删除导致弱引用计数无法被访问。如果我们也想像非侵入式智能指针 一样新开辟一块区域来存储ref count, 那么就变得和shared_ptr/weak_ptr一样,没有重新实现的必要了。所以如今在使用侵入式只能指针并且有循环引用风险的地方,我们会将其中一方持有对方的智能指针修改为原始指针或者是引用。

  1. No comments yet.
  1. No trackbacks yet.