智能指针sp与wp

在AOSP中,Google实现了一套特有的智能指针系统,用来方便C++工程中的内存管理。内存释放是C++工程中非常难处理的一部分,为此一些C++工程都会有自己的智能指针系统,C++11的std中也提供了auto_ptr之类的智能指针的概念。

从auto_ptr说起

我们先看下C++ 11中的auto_ptr,这个比AOSP中的较容易理解。

auto_ptr在memory.h中,Android NDK中也包含此文件,我们可以直接使用std命名空间下面的一些方法,遗憾的是NDK把auto_ptr这部分从memory.h中删掉了,NDK开发没有办法使用auto_ptr。

auto_ptr的用法

看下输出结果

Person的析构函数正常调用了,这是因为Person的指针的生命周期和auto_ptr绑定在一起了,而auto_ptr又是一个分配在栈中的对象,在函数返回是自动销毁,在auto_ptr的析构函数中会自动将Person指针delete。

auto_ptr存在的问题

auto_ptr存在不少的问题,正因为这些原因,Android团队放弃使用auto_ptr作为智能指针的方案。

  • auto_ptr不能引用指针数组,刚才可以看到auto_ptr的析构中只写了delete,并没有使用delete[]进行删除,所以引用指针数组是需要避免的。
  • 一个auto_ptr只能引用一个指针,不能出现两个auto_ptr引用同一个的对象的情况。如果出现了那么会造成这个指针被delete两次。

  • auto_ptr不能用于函数参数,函数参数中使用auto_ptr作为参数的话 ,首先会调用auto_ptr的拷贝构造函数,原先的auto_ptr会将当前引用释放,但是仍然会存在提前释放的问题。

输出结果

可以看出,析构函数在我们对这个对象操作之前进行调用了,这不是我们期望的,出现这个结果的原因是auto_ptr的原理所致,auto_ptr没有引用计数,唯一安全的使用范围就是函数内。

AOSP中的智能指针

概要

AOSP中有两种类型的智能指针,分别是sp和wp,sp表示强指针,强指针会进行引用计数,引用计数是透过该对象自身维持的,当这个对象的引用为0的时候,sp就会释放这个指针。

wp 表示弱指针,弱指针一般出现在两个对象循环引用的场景,如果两个对象A,B互相引用,那么这两个对象的引用计数都不为0,会出现无法释放的情况。所以如果A,B对象需要互相引用,那么A引用B,或者B引用A的时候一定需要一个弱指针,当对象的强指针计数为0时,删除该对象,如果该对象被弱指针引用,那么必须升级为强指针,否则不能进行任何操作。

如果使用sp, wp引用该对象的话, 类必须继承LightRefBase或者RefBase。 下面看下类关系图。

其中还有一个LightRefBase类,可以单独拿出来分析,它的作用跟RefBase类似,但是问题在于如果类继承自LightRefBase的话,是没有办法使用wp的。

简单来说,关键点是RefBase和LightBase的实现,这两个类管理的引用计数,sp,wp只是增加和减少引用计数。 我们看下一个测试

调用这个 refbaseTest时,会在堆中生成一个Object对象,然后交由智能指针进行管理,sp对象生成在栈空间,在方法返回时sp会被析构,此时会更改Object的引用计数,Object的引用计数为0时,会delete 自己。

这个是智能指针最基本的使用,但是AOSP中的使用较为复杂,可能出现下面这些情况 。

sp作为参数传递,此时会调用sp的拷贝构造函数,此时对象引用计数会再+1,caller返回后会再-1,不会对对象释放造成影响。

我们先来看下具体实现吧,更复杂的场景稍后举例说明。

sp

sp的定义如下:

可以看出sp中几乎重写了所有对象常用的操作符,目的就是将所有的调用都传递给自身包含的指针,即RefBase的子类。

我们重点看下构造函数和析构函数。

构造函数的作用就是将引用计数加一,析构函数的作用是将引用计数减一,是否删除对象由RefBase和LightRefBase决定。

LightRefBase

LightRefBase实现较为简单,我们先看下LightRefBase如何管理引用计数的,稍后再看下复杂的RefBase。

可以看出LightRefBase中有一个原子类型的mCount来进行引用计数,incStrong和decStrong均直接对该类的mCount操作。

在decStrong中可以看出,如果mCount的值为0,那么就会将对象自身delete。

LightRefBase是没有办法配合wp进行使用的,LightRefBase类中没有createWeak之类的方法 ,在模板具体化时就会报错,无法编译。

RefBase

重量级的类是RefBase, 智能指针大量的逻辑都是在RefBase类中进行维护的。我们先看下这个类的定义。

可以看出,初始化了一个weakref_impl对象, mRefs是一个weakref_impl对象指针,我们暂时将他们称为影子对象,这个影子对象的负责维护RefBase中的强弱引用个数。

看下RefBase的incStrong实现

可以 看出,该方法是将weakref_impl中的mStrong和mWeak分别加一,mWeak加一是通过incWeak方法进行的。 如果是第一次获得对象引用的话,回调onFirstRef方法。

weak_impl中incWeak方法

decStrong的实现

decStrong的逻辑如下,如果mStrong的个数为0并且该对象的生命周期是由强引用控制,那么就调用delete this将自己删除。 flags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG这个表示对象的生命周期根据强引用还是弱引用控制的,默认是强引用。

如果调用delete this将自身删除,那么会调用RefBase的析构函数。

但是析构函数此时并没有调用delete mRefs将weak_impl对象清除,应该是在decWeak中进行清除。

delete impl 会将weak_impl清除,至此该对象的生命周期就彻底走完了。

wp

下面我们看下弱引用控制的情况,RefBase同时支持强引用计数和弱引用计数,对象的生命周期默认是由强引用进行管理的,可以使用extendObjectLifetime将该对象的生命周期扩展成弱引用,即弱引用为0时,才会调用delete 删除自身。

wp没有办法直接获取到实际对象指针,需要升级为强引用才可以,如果升级(调用promote方法)过程中对象已经被删除,那么promote()方法会返回空的sp,所以,使用wp的时候需要判断下是否为NULL。

这个时候,使用wp.promote将指针提升为sp,使用sp访问对象。

该调用的输出为

可以看出,此次promote没有返回NULL,指针升级成功。

下面看下promote失败的情况。

先看下结果,这次promote会失败,输出结果为:

这次调用是在getwptr()中进行了一次promote,该对象的强引用计数+1,但是随着getwptr返回,强引用计数为0,再次promote会失败。 我们看下具体的promote逻辑。

核心逻辑还是在attemptIncStrong里面的,如果attemptIncStrong返回false,那么会返回空的sp。

代码比较长,我们分段来看。

这段逻辑是,如果强引用的计数大于0,那么直接加一,后面会返回true。

如果这段逻辑的意思是如果是第一次promote,那么引用计数加一返回true。如果不是第一次promote了, 那么强引用计数又等于0,那么返回false,不允许升级为sp。

正好印证了上面两次的结果。

extendObjectLifetime

这个extendObjectLifetime方法用来扩展对象的生命周期,让对象的生命周期是根据强引用还是弱引用进行销毁。默认是强引用。

下面看下示例

结果

如果不更改extendObjectLifetime的话,是不会调用析构函数 的,因为这个对象的强引用个数一直是初始值。 核心删除逻辑在decWeak方法中

最后else这段,会先回调onLastWeakRef,然后使用delete 删除。

总结下AOSP智能指针和auto_ptr的区别

两者的区别很大,sp操作的引用计数是嵌入在被管理对象里面的, auto_ptr操作的引用管理是嵌入在auto_ptr里面的,也就是说,两个auto_ptr不能同时指向同一个被管理对象,但是两个sp可以指向同一个被管理对象。auto_ptr适合在小范围内局部使用,例如函数内部,不适合作为函数参数传来传去,sp可以在全局范围内使用,可以作为函数参数传来传去。两者之间的这些不同在于它们的实现方法不同,最明显的就是sp指向的对象必须要是从RefBase继承下来的,而auto_ptr指向的对象则不需要从某一个指定的类继承下来


参考: https://blog.csdn.net/Luoshengyang/article/details/6786239 http://www.cnblogs.com/ider/archive/2011/07/22/cpp_cast_operator_part2.html http://androidxref.com/9.0.0_r3/xref/system/core/libutils/RefBase.cpp

1 对 “Android中智能指针sp与wp”的想法;

发表评论

电子邮件地址不会被公开。 必填项已用*标注