作者:刘昊昱
博客:http://blog.csdn.net/liuhaoyutz
Android版本:4.4.2
在C++语言中,指针操作是最容易问题的地方,常见的指针操作错误有以下几种:
1、 定义一个指针,但没有对其进行初始化。这种情况下,指针会指向一个随机地址,此时使用该指针,将出现不可预知的错误。一般定义一个指针时,应该同时对该指针进行初始化。
2、 new了一个对象后,忘记delete该对象。这种情况会造成内存泄漏,时间久了,重复多次,可能造成系统宕机。
3、 野指针。例如,我们new了一个对象A,并用指针p1指向A,使用结束后,我们delete了对象A,此时,p1还是指向A原来的地址,但是A被delete后,该地址是非法地址。这样的p1就是野指针。再举一个例子,p1和p2两个指针都指向A,我们通过p1指针delete了A之后,将p1设置为NULL,但p2仍然指向A原来地址,此时,p2就是野指针。
为了避免上述C++指针使用错误,Android为我们提供了智能指针,定义在frameworks/rs/cpp/util目录下的RefBase.h和StrongPointer.h文件中。
Android智能指针是一个模板类,又分为强指针sp和弱指针wp。强指针sp定义如下:
62template<typename T> 63class sp 64{ 65public: 66 inline sp() : m_ptr(0) { } 67 68 sp(T* other); 69 sp(const sp<T>& other); 70 template<typename U> sp(U* other); 71 template<typename U> sp(constsp<U>& other); 72 73 ~sp(); 74 75 // Assignment 76 77 sp& operator = (T* other); 78 sp& operator = (const sp<T>&other); 79 80 template<typename U> sp& operator= (const sp<U>& other); 81 template<typename U> sp& operator= (U* other); 82 83 //! Special optimization for use byProcessState (and nobody else). 84 void force_set(T* other); 85 86 // Reset 87 88 void clear(); 89 90 // Accessors 91 92 inline T& operator* ()const { return *m_ptr; } 93 inline T* operator-> () const {return m_ptr; } 94 inline T* get() const { return m_ptr; } 95 96 // Operators 97 98 COMPARE(==) 99 COMPARE(!=)100 COMPARE(>)101 COMPARE(<)102 COMPARE(<=)103 COMPARE(>=)104105private:106 template<typename Y>friend class sp;107 template<typename Y>friend class wp;108 void set_pointer(T* ptr);109 T* m_ptr;110};
66-71行,定义了5种sp构造函数。
73行,定义了sp的析构函数。
77-81行,定义了4种“=”运算符的重载函数。
92-103行,对其它8种运算符进行重载。每个COMPARE宏对应该运算符的6个重载函数。
109行,定义T类型指针变量m_ptr。这个指针变量m_prt即是sp类的核心。
我们可以这样理解sp类:
1、sp类的对象实例用来替代我们原来所用的指针。
2、sp类是对指针的封装。sp.m_prt即我们原来所用的指针。
3、通过使用sp类的对象代替指针,可以避免出现原来使用指针时常见的错误。
为什么说使用sp类的对象代替指针,就可以避免原来使用指针时常见的错误呢?
首先来看使用指针的第一种常见错误,即定义指针时没有进行初始化。
使用sp类对象代替指针后,创建sp类对象时会调用到sp类构造函数,在构造函数中,会对sp.m_ptr进行初始化,例如:
66 inline sp() : m_ptr(0) { }
默认构造函数将sp.m_ptr初始化为0。
再比如:
120template<typename T>121sp<T>::sp(T* other)122: m_ptr(other)123 {124 if (other)other->incStrong(this);125 }
该构造函数将sp.m_ptr初始化为通过参数传递进来的other。
除了构造函数,对sp对象进行初始化还可能通过赋值运算符,例如:
sp<Object> = new Object
这种情况下,就用到了sp的“=”重载运算符:
162template<typename T>163sp<T>& sp<T>::operator = (T* other)164{165 if (other) other->incStrong(this);166 if (m_ptr)m_ptr->decStrong(this);167 m_ptr = other;168 return *this;169}
可以看到,在“=”重载运算符中,167行,将参数传递进来的other赋值给sp.m_ptr。
这样通过在构造函数和重载赋值运算符中完成对sp.m_ptr的初始化,即避免了使用指针的第一种常见错误(定义指针时忘记初始化)。
使用指针的第二种常见错误(new一个对象后忘记delete)和第三种常见错误(野指针)可以通过给被指针指向的对象加一个引用计数器来解决。我们可以想象一下,如果被指针指向的对象有一个引用计数器,即当有一个指针指向该对象时,该对象引用计数器为1,有两个指针指向该对象时,该对象引用计数器为2,依次类推。反之,当一个指针不再指向该对象时,该对象引用计数器的值减1,当对象引用计数器的值为0时,该对象需要被delete。
怎样给对象设置一个引用计数器呢?Android智能指针的做法是让该对象对应的类继承LightRefBase模板类,该类定义在frameworks/rs/cpp/util/RefBase.h文件中:
163template <class T>164class LightRefBase165{166public:167 inline LightRefBase() :mCount(0) { }168 inline voidincStrong(__attribute__((unused)) const void* id) const {169 __sync_fetch_and_add(&mCount, 1);170 }171 inline voiddecStrong(__attribute__((unused)) const void* id) const {172 if(__sync_fetch_and_sub(&mCount, 1) == 1) {173 deletestatic_cast<const T*>(this);174 }175 }176 //! DEBUGGING ONLY: Getcurrent strong ref count.177 inline int32_tgetStrongCount() const {178 return mCount;179 }180181 typedefLightRefBase<T> basetype;182183protected:184 inline ~LightRefBase() { }185186private:187 friend classReferenceMover;188 inline static void moveReferences(void*,void const*, size_t,189 constReferenceConverterBase&) { }190191private:192 mutable volatile int32_tmCount;193};
192行,定义了一个整数mCount,这就是所谓的引用计数器。
167行,LightRefBase的构造函数将引用计数器mCount初始化为0。
168-170行,定义了incStrong函数,用于将引用计数器mCount的值加1。
171-175行,定义了decStrong函数,用于将引用计数器mCount的值减1,需要注意的是,如果减1之前,mCount的值为1,说明对本对象最后的引用也解除了,则会delete本对象,这样就避免了我们所说的new一个对象,忘记delete。
知道了LightRefBase的定义,我们再回过头来看sp类,就能理解智能指针是怎样工作的了。
sp的构造函数如下:
120template<typename T>121sp<T>::sp(T* other)122: m_ptr(other)123 {124 if (other)other->incStrong(this);125 }
sp的重载赋值运算符如下:
162template<typename T>163sp<T>& sp<T>::operator = (T* other)164{165 if (other)other->incStrong(this);166 if (m_ptr)m_ptr->decStrong(this);167 m_ptr = other;168 return *this;169}
类T继承LightRefBase,可以看到,通过构造函数或赋值运算让sp对象指向T对象,除了将other赋值给sp.m_ptr外,因为这两种情况都属于增加一个对象引用计数,所以还会调用other->incStrong(this)。特别需要注意的是,在赋值运算中,如果m_ptr之前指向其它值,则需要先调用m_ptr->decStrong(this),即对应对象引用计数减1,然后再将other赋值给sp.mptr。
sp的析构函数如下:
147template<typename T>148sp<T>::~sp()149{150 if (m_ptr)m_ptr->decStrong(this);151}
可见,当智能指针sp析构时,会调用m_ptr->decStrong(this)让对应对象的引用计数器减1。