上节通过一个死锁的问题,引入了强弱指针的概念。
本节来讨论Android强弱指针的实现与使用。
先来回顾一下轻量级智能指针。
首先定义了一个 LightRefBase类,它有一个私有成员 mCount,即所谓的引用计数。
然后定义了一个Person类,Person类继承了LightRefBase类,这样Person类就同样有引用计数了。
可以通过 sp
在sp的构造函数中,通过调用incStrong函数,来增加引用计数;在sp的析构函数中,通过调用decStrong函数,来减少引用计数。
当检测到引用计数为0时,就把m_ptr指向的对象释放掉。
这就是轻量级智能指针。
现在,我们来尝试统一实现强指针/弱指针。
之前是LightRefBase,现在去掉轻量Light,变成RefBase。
mCount变成mStrong和mWeak,其中mStrong是强引用计数,和之前mCount的作用相同;mWeak则是弱引用计数。
对于mStrong,有:
对于mWeak,有:
它们的结构应该是这样,mStrong和mWeak是私有成员,void incStrong();等函数是公有成员。
把上述的类抽象出来,定义一个类StrongWeakRef_type。
- class StrongWeakRef_type {
- private:
- int mStrong;
- int mWeak;
-
- public:
- void incStrong();
- void decStrong();
- void incWeak();
- void decWeak();
- };
为了兼容以前的函数,将incStrong和decStrong放在RefBase类中。
另外,在头文件中,我们不希望别人可以看到StrongWeakRef_type的私有成员,mStrong和mWeak;只需要将public成员开放出来即可。
那么可以通过抽象类界面来实现,将StrongWeakRef_type类拆分成两部分:
抽象出一个接口类weakref_type,里面只放接口。
- class Weakref_type {
- public:
- void incWeak();
- void decWeak();
- };
抽象出一个实现类weakref_impl,它继承于weakref_type。
- class Weakref_impl {
- private:
- int mStrong;
- int mWeak;
- };
然后,把相对固定的接口类weakref_type,放在头文件里面;把实现类weakref_impl,放在cpp文件里面。
需要使用时只需要包含头文件即可,而cpp文件,则可以编译成库。
在RefBase.h文件中,有一个RefBase类,它会包含weakref_impl类。
需要注意的是,它包含的是指向weakref_impl类的指针。
那么,为什么包含的是指针而不是 weakref_impl 类本身呢?
答:如果包含的是 weakref_impl 类,那么后续如果 weakref_impl 类的成员有变动(即空间分配有变动),那么也就意味着所有使用到utils\RefBase.h文件的应用都需要重新编译。
这样的话,改动的代价就太大了,而如果只是包含指针,指针只是一个地址,在32位机中只占据4个字节,即使 weakref_impl 类的结构有变动,RefBase的空间布局也不会有影响,还是一个32位的指针。
问:weakref_impl类是在哪里实现的?
答:在安卓源码的RefBase.cpp文件中实现的,它有 mStrong,mWeak和一些其他参数。
并且可以看到,weakref_impl类是从weakref_type中派生出来的,而weakref_type则是在头文件RefBase.h中定义的,它定义了一些接口供外部调用。
当我们使用Person类的时候,首先是定义,Person需要继承RefBase。
- class Person : public RefBase {
- ...
- };
创建一个include目录,从安卓源码把cutils和utils复制过来,放到include目录下。
再把安卓源码中的RefBase.cpp也复制过来。
写一个Makefile来编译代码,在Makefile中指定头文件路径。
最终测试代码如下,中间分析过程省略,后面会提供指令下载可以编译的代码。
- #include <iostream>
- #include <string.h>
- #include <unistd.h>
- #include <utils/RefBase.h>
-
- using namespace std;
- using namespace android;
-
- class Person : public RefBase {
- private:
- sp<Person> father;
- sp<Person> son;
- public:
- void setSon(sp<Person> &other) { this->son = other; }
- void setFather(sp<Person> &other) { this->father = other; }
-
- Person()
- {
- cout << "Person()" << endl;
- }
-
- ~Person()
- {
- cout << "~Person()" << endl;
- }
-
- void printInfo(void)
- {
- cout << "printInfo()" << endl;
- }
- };
-
- void test_func(void)
- {
- sp<Person> father = new Person();
- sp<Person> son = new Person();
-
- father->setSon(son);
- son->setFather(father);
- }
-
- int main(int argc, char **argv)
- {
- test_func();
-
- return 0;
- }
由于使用的是sp,即强指针,所以此时编译会出现和之前一样内存泄漏的问题。
修改为弱指针wp,看一下是否可以解决这个问题。
编译测试,发现析构函数有被调用,即没有发生死锁。
修改main函数,在main函数中调用Person类的printInfo函数。
编译测试,出现报错。
出错的原因在于,对于弱指针,它没有重载'->'。
因为wp只是引用而已,并没有控制对象的生死,所以可能在引用时,对象已经死亡了。
同理,弱指针也没有重载 '*'。
那么,弱指针要怎么使用'->' 和 '*' 呢?
答:将弱指针wp转换为强指针sp之后,就可以使用'->' 和 '*' 了。
wp类中有一个成员函数promote,使用这个函数可以将弱指针转换为强指针。
需要注意的是,promote函数可能转换成功,也可能转换失败,转换失败时返回0。
所以,要对返回值做一个判断,只有转换成功时才能使用。
此时编译测试,就没有问题了。
修改代码,在Person类中增加一个私有成员name,一个构造函数Person(char *name),获取name的成员函数char *getName(void)。
修改printInfo函数,将father和son的名字输出。
修改测试函数test_func,分别设置和打印信息。
在main函数中调用test_func函数。
编译测试,此时可以输出son和father的名字,并且析构函数有被调用。
最后,根据上面的测试可以知道,使用弱指针可以解决强指针中存在的死锁的问题,但是对于弱指针,如果想要调用Person里面的成员函数,必须先使用promote函数将弱指针转换为强指针。
同时需要判断一下返回值,因为弱指针只是简单的返回某个对象而已,它不保证这个对象是否没有消亡。
本节只是简单测试强弱指针的使用方法,对于它们的具体实现方法,待以后深入研究。
本节代码,可以使用以下指令获取:
- git clone https://github.com/weidongshan/cpp_project.git
- git pull origin master
- git checkout v21