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

一、问题的引入

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

#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++编程的本质就是在操作内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值