作者:刘昊昱
博客: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
63class sp
64{
65public:
66 inline sp() : m_ptr(0) { }
67
68 sp(T* other);
69 sp(const sp& other);
70 template sp(U* other);
71 template sp(constsp& other);
72
73 ~sp();
74
75 // Assignment
76
77 sp& operator = (T* other);
78 sp& operator = (const sp&other);
79
80 template sp& operator= (const sp& other);
81 template 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(>=)
104
105private:
106 templatefriend class sp;
107 templatefriend 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
121sp::sp(T* other)
122: m_ptr(other)
123 {
124 if (other)other->incStrong(this);
125 }
该构造函数将sp.m_ptr初始化为通过参数传递进来的other。
除了构造函数,对sp对象进行初始化还可能通过赋值运算符,例如:
sp
这种情况下,就用到了sp的“=”重载运算符:
162template
163sp& sp::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
164class LightRefBase
165{
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(this);
174 }
175 }
176 //! DEBUGGING ONLY: Getcurrent strong ref count.
177 inline int32_tgetStrongCount() const {
178 return mCount;
179 }
180
181 typedefLightRefBase basetype;
182
183protected:
184 inline ~LightRefBase() { }
185
186private:
187 friend classReferenceMover;
188 inline static void moveReferences(void*,void const*, size_t,
189 constReferenceConverterBase&) { }
190
191private:
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
121sp::sp(T* other)
122: m_ptr(other)
123 {
124 if (other)other->incStrong(this);
125 }
sp的重载赋值运算符如下:
162template
163sp& sp::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
148sp::~sp()
149{
150 if (m_ptr)m_ptr->decStrong(this);
151}
可见,当智能指针sp析构时,会调用m_ptr->decStrong(this)让对应对象的引用计数器减1。