ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
我们的“三板斧”,其实就是三个例子。相信这三板斧劈下去,你会很容易理解它们。 **例子1** ~~~ /类A从RefBase派生,RefBase是万物的始祖 class A:public RefBase { //A没有任何自己的功能 } int main() { A* pA =new A; { //注意我们的sp,wp对象是在{}中创建的,下面的代码先创建sp,然后创建wp sp<A>spA(A); wp<A>wpA(spA); //大括号结束前,先析构wp,再析构sp } } ~~~ 例子够简单吧?但也需一步一步分析这斧子是怎么劈下去的。 1. RefBase和它的影子 类A从RefBase中派生。使用的是RefBase构造函数。代码如下所示: **RefBase.cpp** ~~~ RefBase::RefBase() :mRefs(new weakref_impl(this))//注意这句话 { //mRefs是RefBase的成员变量,类型是weakref_impl,我们暂且叫它影子对象 //所以A有一个影子对象 } ~~~ mRefs是引用计数管理的关键类,需要进去观察。它是从RefBase的内部类weakref_type中派生出来的。 先看看它的声明: ~~~ class RefBase::weakref_impl : public RefBase::weakref_type //从RefBase的内部类weakref_type派生 ~~~ 由于Android频繁使用C++内部类的方法,所以初次阅读Android代码时可能会有点不太习惯,C++的内部类和Java内部类相似,但不同的是,它需要一个显示的成员指向外部类对象,而Java内部类对象就有一个隐式的成员指向外部类对象。 说明:内部类在C++中的学名叫nested class(内嵌类)。 **RefBase.cpp::weakref_imple构造** ~~~ weakref_impl(RefBase* base) :mStrong(INITIAL_STRONG_VALUE) //强引用计数,初始值为0x1000000 ,mWeak(0) //弱引用计数,初始值为0 ,mBase(base)//该影子对象所指向的实际对象 ,mFlags(0) ,mStrongRefs(NULL) ,mWeakRefs(NULL) ,mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT) ,mRetain(false) { } ~~~ 如你所见,new了一个A对象后,其实还new了一个weakref_impl对象,这里称它为影子对象,另外我们称A为实际对象。 这里有一个问题:影子对象有什么用? 可以仔细想一下,是不是发现影子对象成员中有两个引用计数?一个强引用,一个弱引用。如果知道引用计数和对象生死有些许关联的话,就容易想到影子对象的作用了。 按上面的分析,在构造一个实际对象的同时,还会悄悄地构造一个影子对象,在嵌入式设备的内存不是很紧俏的今天,这个影子对象的内存占用已不成问题了。 2. sp上场 程序继续运行,现在到了 ~~~ sp<A> spA(A); ~~~ 请看sp的构造函数,它的代码如下所示:(注意,sp是一个模板类,对此不熟悉的读者可以去翻翻书,或者干脆把所有出现的T都换成A。) **RefBase.h::sp(T*other)** ~~~ template<typename T> sp<T>::sp(T* other) //这里的other就是刚才创建的pA :m_ptr(other)// sp保存了pA的指针 { if(other) other->incStrong(this);//调用pA的incStrong } ~~~ OK,战场转到RefBase的incStrong中。它的代码如下所示: **RefBase.cpp** ~~~ void RefBase::incStrong(const void* id) const { //mRefs就是刚才RefBase构造函数中new出来的影子对象 weakref_impl*const refs = mRefs; //操作影子对象,先增加弱引用计数 refs->addWeakRef(id); refs->incWeak(id); ...... ~~~ 先来看看影子对象的这两个weak函数都干了些什么。 (1)眼见而心不烦 先来看第一个函数addWeakRef,代码如下所示: **RefBase.cpp** ~~~ void addWeakRef(const void* /*id*/) { } ~~~ 呵呵,addWeakRef啥都没做,因为这是release版走的分支。调试版的代码我们就不讨论了,它是给创造RefBase、 sp,以及wp的人调试用的。 调试版分支的代码很多,看来创造它们的人,也为不理解它们之间的暧昧关系痛苦不已。 总之,一共有这么几个不用考虑的函数,我们都已列出来了。以后再碰见它们,干脆就直接跳过的是: ~~~ void addStrongRef(const void* /*id*/) { } void removeStrongRef(const void* /*id*/) { } void addWeakRef(const void* /*id*/) { } void removeWeakRef(const void* /*id*/) { } void printRefs() const { } void trackMe(bool, bool) { } ~~~ 继续我们的征程。再看incWeak函数,代码如下所示: **RefBase.cpp** ~~~ void RefBase::weakref_type::incWeak(const void*id) { weakref_impl* const impl = static_cast<weakref_impl*>(this); impl->addWeakRef(id); //上面说了,非调试版什么都不干 const int32_tc = android_atomic_inc(&impl->mWeak); //原子操作,影子对象的弱引用计数加1 //千万记住影子对象的强弱引用计数的值,这是彻底理解sp和wp的关键 } ~~~ 好,我们再回到incStrong,继续看代码: **RefBase.cpp** ~~~ ...... //刚才增加了弱引用计数 //再增加强引用计数 refs->addStrongRef(id);//非调试版这里什么都不干 //下面函数为原子加1操作,并返回旧值。所以c=0x1000000,而mStrong变为0x1000001 const int32_t c =android_atomic_inc(&refs->mStrong); if (c!= INITIAL_STRONG_VALUE) { //如果c不是初始值,则表明这个对象已经被强引用过一次了 return; } //下面这个是原子加操作,相当于执行refs->mStrong +(-0x1000000),最终mStrong=1 android_atomic_add(-INITIAL_STRONG_VALUE,&refs->mStrong); /* 如果是第一次引用,则调用onFirstRef,这个函数很重要,派生类可以重载这个函数,完成一些 初始化工作。 */ const_cast<RefBase*>(this)->onFirstRef(); } ~~~ 说明:android_atomic_xxx是Android平台提供的原子操作函数,原子操作函数是多线程编程中的常见函数,读者可以学习原子操作函数知识,本章后面将对其做介绍。 (2)sp构造的影响 sp构造完后,它给这个世界带来了什么? - 那就是RefBase中影子对象的强引用计数变为1,弱引用计数也变为1。 更准确的说法是,sp的出生导致影子对象的强引用计数加1,弱引用计数加1。 (3)wp构造的影响 继续看wp,例子中的调用方式如下: ~~~ wp<A> wpA(spA) ~~~ wp有好几个构造函数,原理都一样。来看这个最常见的: **RefBase.h::wp(constsp<T>& other)** ~~~ template<typename T> wp<T>::wp(const sp<T>& other) :m_ptr(other.m_ptr) //wp的成员变量m_ptr指向实际对象 { if(m_ptr) { //调用pA的createWeak,并且保存返回值到成员变量m_refs中 m_refs = m_ptr->createWeak(this); } } ~~~ **RefBase.cpp** ~~~ RefBase::weakref_type* RefBase::createWeak(constvoid* id) const { //调用影子对象的incWeak,这个我们刚才讲过了,将导致影子对象的弱引用计数增加1 mRefs->incWeak(id); returnmRefs; //返回影子对象本身 } ~~~ 我们可以看到,wp化后,影子对象的弱引用计数将增加1,所以现在弱引用计数为2,而强引用计数仍为1。另外,wp中有两个成员变量,一个保存实际对象,另一个保存影子对象。sp只有一个成员变量用来保存实际对象,但这个实际对象内部已包含了对应的影子对象。 OK,wp创建完了,现在开始进入wp的析构。 (4)wp析构的影响 wp进入析构函数,这表明它快要离世了。 **RefBase.h** ~~~ template<typename T> wp<T>::~wp() { if(m_ptr) m_refs->decWeak(this); //调用影子对象的decWeak,由影子对象的基类实现 } ~~~ **RefBase.cpp** ~~~ void RefBase::weakref_type::decWeak(const void*id) { //把基类指针转换成子类(影子对象)的类型,这种做法有些违背面向对象编程的思想 weakref_impl*const impl = static_cast<weakref_impl*>(this); impl->removeWeakRef(id);//非调试版不做任何事情 //原子减1,返回旧值,c=2,而弱引用计数从2变为1 constint32_t c = android_atomic_dec(&impl->mWeak); if (c !=1) return; //c=2,直接返回 //如果c为1,则弱引用计数为0,这说明没用弱引用指向实际对象,需要考虑是否释放内存 // OBJECT_LIFETIME_XXX和生命周期有关系,我们后面再说。 if((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { if(impl->mStrong == INITIAL_STRONG_VALUE) delete impl->mBase; else { delete impl; } } else{ impl->mBase->onLastWeakRef(id); if((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) { delete impl->mBase; } } } ~~~ OK,在例1中,wp析构后,弱引用计数减1。但由于此时强引用计数和弱引用计数仍为1,所以没有对象被干掉,即没有释放实际对象和影子对象占据的内存。 (5)sp析构的影响 下面进入sp的析构。 **RefBase.h** ~~~ template<typename T> sp<T>::~sp() { if(m_ptr) m_ptr->decStrong(this); //调用实际对象的decStrong。由RefBase实现 } ~~~ **RefBase.cpp** ~~~ void RefBase::decStrong(const void* id) const { weakref_impl* const refs = mRefs; refs->removeStrongRef(id);//调用影子对象的removeStrongRef,啥都不干 //注意,此时强弱引用计数都是1,下面函数调用的结果是c=1,强引用计数为0 constint32_t c = android_atomic_dec(&refs->mStrong); if (c== 1) { //对于我们的例子, c为1 //调用onLastStrongRef,表明强引用计数减为0,对象有可能被delete const_cast<RefBase*>(this)->onLastStrongRef(id); //mFlags为0,所以会通过delete this把自己干掉 //注意,此时弱引用计数仍为1 if((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { delete this; } ...... } ~~~ 先看delete this的处理,它会导致A的析构函数被调用;再看A的析构函数,代码如下所示: **例子1::~A()** ~~~ //A的析构直接导致进入RefBase的析构。 RefBase::~RefBase() { if(mRefs->mWeak == 0) { //弱引用计数不为0,而是1 delete mRefs; } } ~~~ RefBase的delete this自杀行为没有把影子对象干掉,但我们还在decStrong中,可接着从delete this往下看: **RefBase.cpp** ~~~ ....//接前面的delete this if ((refs->mFlags&OBJECT_LIFETIME_WEAK)!= OBJECT_LIFETIME_WEAK) { delete this; } //注意,实际数据对象已经被干掉了,所以mRefs也没有用了,但是decStrong刚进来 //的时候就保存mRefs到refs了,所以这里的refs指向影子对象 refs->removeWeakRef(id); refs->decWeak(id);//调用影子对象decWeak } ~~~ **RefBase.cpp** ~~~ void RefBase::weakref_type::decWeak(const void*id) { weakref_impl*const impl = static_cast<weakref_impl*>(this); impl->removeWeakRef(id);//非调试版不做任何事情 //调用前影子对象的弱引用计数为1,强引用计数为0,调用结束后c=1,弱引用计数为0 constint32_t c = android_atomic_dec(&impl->mWeak); if (c!= 1) return; //这次弱引用计数终于变为0,并且mFlags为0, mStrong也为0。 if((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { if(impl->mStrong == INITIAL_STRONG_VALUE) delete impl->mBase; else { delete impl; //impl就是this,把影子对象自己干掉 } } else{ impl->mBase->onLastWeakRef(id); if((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) { delete impl->mBase; } } } ~~~ 好,第一板斧劈下去了!来看看它的结果是什么。 3. 第一板斧的结果 第一板斧过后,来总结一下刚才所学的知识: - RefBase中有一个隐含的影子对象,该影子对象内部有强弱引用计数。 - sp化后,强弱引用计数各增加1,sp析构后,强弱引用计数各减1。 - wp化后,弱引用计数增加1,wp析构后,弱引用计数减1。 完全彻底地消灭RefBase对象,包括让实际对象和影子对象灭亡,这些都是由强弱引用计数控制的,另外还要考虑flag的取值情况。当flag为0时,可得出如下结论: - 强引用为0将导致实际对象被delete。 - 弱引用为0将导致影子对象被delete。