c++智能指针的原理与简单实现

来源:互联网 发布:逆袭网络剧百度云网盘 编辑:程序博客网 时间:2024/06/11 20:42

一、问题的引入

先看一段简单的代码如下:

#include<iostream>using namespace std;class Person {private:char *name ;public:Person() {cout<<"Person()"<<endl ;}~Person() {cout<<"~Person()"<<endl ;}} ;void test(void){Person *p = new Person() ;}int main(int argc , char** argv){test();return 0 ;}

 在这段代码里面,定义了一个简单的类Person,它只有一个name成员和简单的构造函数和析构函数。在test函数里面,使用Person *p = new Person()构造了一个person对象,并在main函数里面调用了test()。很明显,在test()里面并没有去delete p ,将不会调用析构函数,因此可能在main函数退出前造成内存泄漏。

 我们当然可以在test()函数里面将“Person *p = new Person()  ” 替换成 “Person p ; ” ,对应为p分配的内存是在栈里面,因此在test()函数退出前会自动释放分配的内存(执行析构函数),但是这样就无法使用指针操作了(除非我们每次手工执行delete操作)。

 

二 、 智能指针的引入

 定义一个Sp类,如下:

#include<iostream>using namespace std;class Person {private:char *name ;public:Person() {cout<<"Person()"<<endl ;}~Person() {cout<<"~Person()"<<endl ;}} ;class Sp {private:Person *p ;public:Sp(Person *per){cout<<"Sp(Person *per)"<<endl ;p = per ;}~Sp(){cout<<"~Sp()"<<endl ;if(p){delete p ;p = NULL ;}}} ;void test(void){//Person *p = new Person() ;Sp s = new Person() ;}int main(int argc , char** argv){test();return 0 ;}

 

在test()函数里面,我们同样使用new来构造出来一个Person的实例化对象,与前面不同的是,这个对象指针作为一个参数传给Sp类的构造函数构造出来了一个实例化对象s。当test()函数执行完后,对象s的析构函数被调用,在析构函数里面再去delete Person的实例化对象。这是一种非常巧妙的方法,使用Sp s 代替了 Person *  , 由Sp为我们做好了delete操作,这样即使不手工执行delete操作也不会造成内存泄漏的问题 。

因此,此时的Sp s 就可以替代 Person * 指针,这就是智能指针的智能之处,最终目的还是为了防止我们因为忘记delete操作而造成内存泄漏。

 

接下来我们在Person类里面添加一个普通的成员函数printInfo

void printInfo(void){cout<<"just for test !"<<endl ;}

在test()函数里面,我们要调用p的printInfo,即 s->printInfo( ) ,但是Sp 类里面并没有定义这个函数,我们可以在Sp类里面重载“->”:

Person * operator->(){return p ;}

同样,我们也应该重载“*”,它返回对象的引用:

Person& operator*(){return *p ;}
这样,就可以使用s->printInfo( ) 或者 (*s).printInfo() ;

 

三 、 智能指针的完善

1、为智能指针(Sp)添加拷贝构造函数

代码如下:

class Sp {private:Person *p ;public:/*使用Person *来构造*/Sp(Person *per){cout<<"Sp(Person *per)"<<endl ;p = per ;}/*拷贝构造函数*/Sp(const Sp &other){cout<<"Sp(Sp &other)"<<endl ;p = other.p;}~Sp(){cout<<"~Sp()"<<endl ;if(p){delete p ;p = NULL ;}}Person * operator->(){return p ;}} ;

在这个Sp类里面,我们实现了两个构造函数:使用Person * 来构造、使用拷贝构造函数构造。

这里要注意:拷贝构造函数中要将参数Sp & 这个引用声明为const,否则编译器会报错!!!(编译器不知道先使用Person * 来构造还是直接使用拷贝构造函数构造 s)。

 

2、添加引用计数

先看看如下代码:

#include<iostream>using namespace std;class Person {private:char *name ;public:Person() {cout<<"Person()"<<endl ;}~Person() {cout<<"~Person()"<<endl ;}void printInfo(void){cout<<"just for test !"<<endl ;}} ;class Sp {private:Person *p ;public:/*使用Person *来构造*/Sp(Person *per){cout<<"Sp(Person *per)"<<endl ;p = per ;}/*拷贝构造函数*/Sp(const Sp &other){cout<<"Sp(Sp &other)"<<endl ;p = other.p;}~Sp(){cout<<"~Sp()"<<endl ;if(p){delete p ;p = NULL ;}}} ;int main(int argc , char** argv){Sp s = new Person() ;Sp s2 = s;               return 0 ;}

编译、运行该程序 ,发现程序崩溃。

分析原因:

(1)由前一点分析,在main函数里,它首先构造出来一个Person 的实例化对象(假设为p),并使s的p成员指向new出来的p;

(2)接着执行Sp s2 = s,将调用Sp的拷贝构造函数,在里面的s2 的p成员同样指向传入的s的p成员;

(3)main函数退出之前,将分别调用s、s2的析构函数,它们均delete p对应的内存;

因此,问题在于p对象被销毁了两次,这是明显不合理的。第一次释放了p对应的内存后,p = NULL;第二次来释放时,对象已经不存在了,main函数找不到p对象,会出现异常,程序自然崩溃了。

这里,通常的解决方法是给对象p添加一个引用计数,两次构造后p被引用了两次,引用计数为2;当第一次调用Sp的析构函数时,由于对象p仍然被引用,所以先不销毁它,仅仅减少它的引用计数为1;第二次调用Sp的析构函数时,先减少p的引用计数为0,即不被任何对象所引用了,再来销毁p。

(1)为Person添加引用计数,并增加相应接口,在第一次构造Person时初始化计数值为0:

class Person {private:char *name ;/*添加引用计数*/int count;public:void incStrong() {count ++ ;}void decStrong() {count -- ;}int getStrong() {return count ;}Person(): count(0) {cout<<"Person()"<<endl ;}~Person() {cout<<"~Person()"<<endl ;}void printInfo(void){cout<<"just for test !"<<endl ;}} ;

(2)在Sp的构造函数里面,调用Person的incStrong增加引用;在析构函数里面,先调用decStrong减少引用,若此时引用值为0,则销毁它:

class Sp {private:Person *p ;public:/*使用Person *来构造*/Sp(Person *per){cout<<"Sp(Person *per)"<<endl ;p = per ;p->incStrong();}/*拷贝构造函数*/Sp(const Sp &other){cout<<"Sp(Sp &other)"<<endl ;p = other.p;p->incStrong() ;}~Sp(){cout<<"~Sp()"<<endl ;if(p){p->decStrong() ;if(p->getStrong() == 0){delete p ;p = NULL ;}}}} ;

3、使用类模板

我们可以将引用计数独立出来,实现一个名为RefBase的基类,并将Sp类定义为模板类:

#include<iostream>using namespace std;class RefBase {private:int count;public:RefBase() : count(0) {} ;void incStrong() {count ++ ;}void decStrong() {count -- ;}int getStrong() {return count ;}};class Person :public RefBase{private:char *name ;public:Person() {cout<<"Person()"<<endl ;}~Person() {cout<<"~Person()"<<endl ;}void printInfo(void){cout<<"just for test !"<<endl ;}} ;template <typename T>class Sp {private:T *p ;public:/*使用Person *来构造*/Sp(T *per){cout<<"Sp(Person *per)"<<endl ;p = per ;p->incStrong();}/*拷贝构造函数*/Sp(const Sp &other){cout<<"Sp(Sp &other)"<<endl ;p = other.p;p->incStrong() ;}~Sp(){cout<<"~Sp()"<<endl ;if(p){p->decStrong() ;if(p->getStrong() == 0){delete p ;p = NULL ;}}}T * operator->(){return p ;}T& operator*(){return *p ;}} ;int main(int argc , char** argv){Sp<Person> s = new Person() ;Sp<Person> s2 = s;s->printInfo();(*s).printInfo();return 0 ;}


总结:

我们可以使用智能指针代替平时的指针操作 ,使用智能指针来自动维护对象的生命周期。实际上,智能指针的目的就是防止在new操作后忘记delete操作从而造成内存泄漏,它仅仅是一种手段。Android源代码庞大且复杂,大量地采用智能指针来维护对象的生命周期,而在实际编程中,我们要时刻关注对象的生命周期,不应该依赖于这些手段而忽视对内存的重视,因为c/c++编程的本质就是在操作内存。

0 0