Android中的强弱引用计数(强弱指针)

来源:互联网 发布:c语言倒九九乘法表 编辑:程序博客网 时间:2024/06/05 06:19

强指针与弱指针

在Android的源代码中,经常会看到形如:sp、wp这样的类型定义,这其实是Android中的智能指针。智能指针是C++中的一个概念,通过基于引用计数的方法,解决对象的自动释放的问题。在C++编程中,有两个很让人头痛的问题:一是忘记释放动态申请的对象从而造成内存泄露;二是对象在一个地方释放后,又在别的地方被使用,从而引起内存访问错误。程序员往往需要花费很大精力进行精心设计,以避免这些问题的出现。在使用智能指针后,动态申请的内存将会被自动释放(有点类似Java的垃圾回收),不需要再使用delete来释放对象,也不需要考虑一个对象是否已经在其它地方被释放了,从而使程序编写工作减轻不少,而程序的稳定性大大提高。

Android的智能指针相关的源代码在下面两个文件中:frameworks/base/include/utils/RefBase.hframeworks/base/libs/utils/RefBase.cpp

Android中定义了两种智能指针类型,一种是强指针sp(strong pointer),一种是弱指针(weak pointer)。其实成为强引用和弱引用更合适一些。强指针与一般意义的智能指针概念相同,通过引用计数来记录有多少使用者在使用一个对象,如果所有使用者都放弃了对该对象的引用,则该对象将被自动销毁。

弱指针也指向一个对象,但是弱指针仅仅记录该对象的地址,不能通过弱指针来访问该对象,也就是说不能通过弱指针来调用对象的成员函数或访问对象的成员变量。要想访问弱指针所指向的对象,需首先将弱指针升级为强指针(通过wp类所提供的promote()方法)。弱指针所指向的对象是有可能在其它地方被销毁的,如果对象已经被销毁,wp的promote()方法将返回空指针,这样就能避免出现地址访问错的情况。

是不是很神奇?弱指针是怎么做到这一点的呢?其实说穿了一点也不复杂,原因就在于每一个可以被智能指针引用的对象都同时被附加了另外一个weakref_impl类型的对象,这个对象中负责记录对象的强指针引用计数和弱指针引用计数。这个对象是智能指针的实现内部使用的,智能指针的使用者看不到这个对象。弱指针操作的就是这个对象,只有当强引用计数和弱引用计数都为0时,这个对象才会被销毁。

说了这么多原理,下面该看看到底智能指针该怎么使用了。假设现在有一个类MyClass,如果要使用智能指针来引用这个类的对象,那么这个类需满足下列两个前提条件:

(1) 这个类是基类RefBase的子类或间接子类;(2) 这个类必须定义虚构造函数,即它的构造函数需要这样定义:      virtual ~MyClass();满足了上述条件的类就可以定义智能指针了,定义方法和普通指针类似。比如普通指针是这样定义:MyClass* p_obj;智能指针是这样定义:sp<MyClass> p_obj;注意不要定义成 sp<MyClass>* p_obj。初学者容易犯这种错误,这样实际上相当于定义了一个指针的指针。尽管在语法上没有问题,但是最好永远不要使用这样的定义。定义了一个智能指针的变量,就可以象普通指针那样使用它,包括赋值、访问对象成员、作为函数的返回值、作为函数的参数等。比如:
   p_obj = new MyClass(); // 注意不要写成 p_obj = new sp<MyClass>    sp<MyClass> p_obj2 = p_obj;    p_obj->func();    p_obj = create_obj();    some_func(p_obj);

注意不要试图delete一个智能指针,即 delete p_obj。不要担心对象的销毁问题,智能指针的最大作用就是自动销毁不再使用的对象。不需要再使用一个对象后,直接将指针赋值为NULL即可:

    p_obj = NULL;

上面说的都是强指针,弱指针的定义方法和强指针类似,但是不能通过弱指针来访问对象的成员。下面是弱指针的示例:

wp<MyClass> wp_obj = new MyClass();    p_obj = wp_obj.promote(); // 升级为强指针。不过这里要用.而不是->,真是有负其指针之名啊    wp_obj = NULL;

智能指针用起来是很方便,在一般情况下最好使用智能指针来代替普通指针。但是需要知道一个智能指针其实是一个对象,而不是一个真正的指针,因此其运行效率是远远比不上普通指针的。所以在对运行效率敏感的地方,最好还是不要使用智能指针为好。

Android C++框架层的引用计数技术

C++中对指针的使用时很头疼的事情,一个是经常会忘记free 指针,造成内存泄露,另外一个就是野指针问题:访问已经free掉的指针。程序debug工作的相当大部分,都是花费在这。Android中通过引用计数来自动管理指针的生命周期,动态申请的内存将会在不再需要时被自动释放(有点类似Java的垃圾回收),不用程序员明确使用delete来释放对象,也不需要考虑一个对象是否已经在其它地方被释放了,从而使程序编写工作减轻不少,而程序的稳定性也大大提高。

Android提供的引用计数技术,主要是通过RefBase类及其子类sp (strong pointer)和wp(weak pointer)实现的。

引用计数的问题

任何东西都不会是万能的,Android C++中的引用计数问题,和Java一样,并不能完全避免内存泄露,另外还有一个问题就是性能(overhead)问题也很突出,本文就不说了。

使用了android的sp指针,也不能说就不需要程序员去关心指针的细节了。通常由于设计或使用的不良,更有可能导致内存无法回收,也就是内存泄露问题,甚至于还可能导致不明就里的野指针问题。而此时导致的内存使用问题,由于其具有更多的欺骗性和隐蔽性,往往更难发觉和调试。

循环引用及其解法

使用引用计数的智能指针管理方法中,常见的java内存泄露问题在C++中一样存在。在Android的强指针引用中,一个最常见的就是强指针的循环引用问题。而这又是程序员比较容易犯的问题:在程序员对强弱指针的理解不是很深入的情况下,想当然的认为使用了强指针,系统会根据引用计数自动收回。

循环引用,就是对象A有个强指针,引用对象B;对象B中,也有个强指针,引用对象A;这样A和B就互锁。A对象释放B对象的引用是在本身被析构回收时,而析构回收的前提是A对象没有被引用,则需要B对象先释放,B对象释放的前提是A对象释放…如此则A和B都无法释放,这样即产生了内存泄露(内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。 内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。)。

下面举一个内存泄漏的例子:

namespace android{  class Bigclass : public RefBase   {   public:      Bigclass(char *name){          strcpy(mName, name);          ALOGD("Construct: %s", mName);      }      ~Bigclass(){          ALOGD("destruct: %s", mName);      }      void setStrongRefs(sp<Bigclass> b){          spB = b;      }         private:          sp<Bigclass> spB;          char mName[64];   };  }  

该类非常简单,只有一个sp指针和一个name成员。循环引用示例:

void testStrongCrossRef(){      sp<Bigclass> A = new Bigclass("testStrongClassA");      sp<Bigclass> B = new Bigclass("testStrongClassB");      A->setStrongRefs(B);      B->setStrongRefs(A);  }  int main(){      ALOGD("Start testStrongClasses..");        testStrongCrossRef();      ALOGD("testStrongClasses Should be destructed!!");      return 0;  }

输出的结果,如预期,对象没有被释放,泄露了:

D/TEST    ( 1552): Start testStrongClasses..  D/TEST    ( 1552): Construct: testStrongClassA  D/TEST    ( 1552): Construct: testStrongClassB  D/TEST    ( 1552): testStrongClasses Should be destructed!!  

为了解决这一问题,Android在又引入了弱指针,弱指针并不能通过引用计数来控制所引用对象的生命周期,这样就可以消除上例中的引用环路问题,使得问题解决。我们将上述的类稍作修改,增加了弱引用的接口:

namespace android{  class Bigclass : public RefBase   {   public:      Bigclass(char *name){          strcpy(mName, name);          ALOGD("Construct: %s", mName);      }      ~Bigclass(){          ALOGD("destruct: %s", mName);      }      void setStrongRefs(sp<Bigclass> b){          spB = b;      }             void setWeakRefs(sp<Bigclass> b){          wpB = b;      }    private       sp<Bigclass> spB;      wp<Bigclass> wpB;          char mName[64];   };  } 

先来测试一下,将上例中的强指针换成弱指针,会是什么情况:

void testWeakCrossRef(){   sp<Bigclass> A = new Bigclass("testWeakClassA");   sp<Bigclass> B = new Bigclass("testWeakClassB");    A->setWeakRefs(B);    B->setWeakRefs(A);  }  

输出结果:

D/TEST    ( 2889): Start testWeakClass ..  D/TEST    ( 2889): Construct: testWeakClassA  D/TEST    ( 2889): Construct: testWeakClassB  D/TEST    ( 2889): destruct: testWeakClassB  D/TEST    ( 2889): destruct: testWeakClassA  D/TEST    ( 2889): testWeakClass Should be destructed!!  

在出了testWeakClassA和testWeakClassB在对象A和B出了作用域后,没有强引用了,两个对象都释放了,这个符合预期。
这里testWeakClassA和testWeakClassB之间的引用关系,全部都是弱引用,因此二者间的生命周期互不相干,这里二者用sp对象A和B与创建一般的栈对象 Bigclass A, Bigclass B 的生命周期一样。

Android中,最常用的肯定不是下面两种:

1.强强引用——互不相让,互相绑死,这是绝对禁止的。
2.弱弱引用——互不相干,各管生死。这个对于想要使用引用计数自动管理对象生命周期来说,没什么用处。

最常用的一般是强弱引用关系。强弱引用需要是有从属关系的,具体那个类是用sp引用,哪个是用wp引用,要看设计的逻辑了。

测试示例:

void testCrossRef(){   sp<Bigclass> A = new Bigclass("testNormalClassA");   sp<Bigclass> B = new Bigclass("testNormalClassB");    A->setStrongRefs(B);    B->setWeakRefs(A);  }  

输出结果:

D/TEST    ( 2889): Start test Normal pointer reference ..  D/TEST    ( 2889): Construct: testNormalClassA  D/TEST    ( 2889): Construct: testNormalClassB  D/TEST    ( 2889): destruct: testNormalClassA  D/TEST    ( 2889): destruct: testNormalClassB  D/TEST    ( 2889): Test Normal pointer reference Should be destructed!!  

这种情况下,消除了循环引用,没有了内存泄露问题。 和上一个弱弱引用的例子比较,这里testNormalClassB的析构在testWeakClassA之后,testWeakClassB的生命周期是受testWeakClassA控制的,只有testWeakClassA析构,testWeakClassB才会析构。(上面的弱弱引用的测例,说明在无干预的情况下,应该是testWeakClassB先析构的)

对于强弱指针的使用,使用弱指针是需要特别注意,弱指针指向的对象,可能已经被销毁了,使用前需要通过promote()方法探测一下。

野指针问题

强弱引用的问题,相信大多数Android程序员都明白,这里主要要强调一点就是:使用的时候要小心,一不小心可能就出错,举个例子:

我们在刚才定义的BigClass中增加另外一个构造函数:

Bigclass(char *name, char * other){      strcpy(mName, name);      ALOGD("Construct another: %s", mName);      setWeakRefs(this);  }  

这个构造函数,是将本对象中wp类型成员变量的用自己构造,也就是wp指针指向自己,这是允许的。

在写个测试用例:

void testCrash(){      sp<Bigclass> A = new Bigclass("testCrashA", "testCrash");      sp<Bigclass> B = new Bigclass("testCrashB");      A->setWeakRefs(B);      B->setWeakRefs(A);  }  

输出结果:

D/TEST    ( 3709): Construct another: testCrashA  D/TEST    ( 3709): destruct: testCrashA  D/TEST    ( 3709): Construct: testCrashB  D/TEST    ( 3709): destruct: testCrashB  

好像没有什么问题,程序也没有崩溃呀?
没有崩溃,那是幸运,因为这个测试代码和上下文太简单了。 我们看下输出就知道了:testCrashB对象构造的时候, testClassA已经析构了!!!!

也就是说,A对象,在其创建后,马上就消亡了,testCrash()方法中操作的A对象所指向的,都是野指针!!!

为何会出现野指针?问题出在刚才定义的构造函数Bigclass(char name, char other)中。

setWeakRefs(this);  

这里的this是一个Bigclass *类型的,这样在参数压栈的时候需要构建一个临时的sp强指针对象,调用完成后,该对象析构。该sp对象通过sp的sp

Bigclass(char *name, char * other){      ALOGD("start Construct another: %s,", mName);      strcpy(mName, name);      setWeakRefs(this);      ALOGD("end Construct another: %s,", mName);  }  

看一下析构是否在start和end之间。跑一下,你会有意想不到的打印

这里可以给一条规则:
绝对不能在创建的RefBase对象还没有被一个确定的长作用域sp对象引用前,通过局部短作用域sp指针引用。

关于sp和wp:

C++中,sp或wp是一个对象,不是指针。
sp或wp引用的对象,是从堆上new出来的
sp或wp的作用,只是让用户不用太关注delete的调用,sp在析构时,检查堆上对象的引用情况,如果没有别的引用,sp析构自动帮忙调用
wp主要是为解决循环引用的问题。当类比较复杂时,循环引用会很常见。

转自:

http://blog.csdn.net/freshui/article/details/9049193
http://blog.csdn.net/typename/article/details/5808473

0 0