智能指针——C++

智能指针——C++

 在C和C++的代码编辑的过程中,我们经常会自己动态的开辟空间,但是随着代码量的增加难免在有些时候,会忘记释放动态 开辟的空间,此时就会造成内存的泄漏,那么我们怎么样才能避免这样的事情呢?就是用我们的智能指针。
智能指针:定义一个类,来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数中完成资源的清理。
首先我们先来介绍第一种智能指针:AutoPtr
在AutoPtr中,我们要让对象代替我们管理语段空间,那么我们就可以利用析构函数进行空间的释放,但是又因为我们并不知道 我们
即将掌控的是什么类型的空间,所以我们在代码设计的时候就把类给成模版。
 下面是AutoPtr的代码,我们在代码中讲解:
template<class T>
class AutoPtr
{
private:
	T* _ptr;//用类的成员对象来管理一段空间
public:
	//(构造函数)用传进来的空间给对象赋值
	AutoPtr(const T* ptr = NULL):_ptr(ptr)
	{}
	//(拷贝构造函数)用赋值空间来覆盖被赋值空间
	AutoPtr(const AutoPtr<T>& ap):_ptr(ap._ptr)
	{
		_ptr = NULL;//将被赋值空间置空
	}
	//赋值运算符重载
	AutoPtr& operator=(const AutoPtr<T>& ap)
	{
		if(this != &ap)//检测是不是自己给自己赋值
		{
			if(_ptr)//如果当前对象还掌管着一段空间,那么先把这块空间销毁
				delete _ptr;
			_ptr = ap._ptr;//把ap的空间赋值给当前对象
			ap._ptr = NULL;//再把ap的空间置空
		}
		return *this;
	}

	//*运算符重载
	T& operator*()
	{
		return *_ptr;
	}
	//->运算符重载
	T* operator->()
	{
		return _ptr;
	}
	//(析构函数)
	~AutoPtr()
	{
		if(_ptr)//如果当前对象掌管了一块空间,再去销毁
		{
			delete _ptr;
		}
	}
};

AutoPtr的原理就是进行资源的转移,利用掌管空间对象的析构函数来进行资源的销毁,但是我们不难发现,如果我们用
拷贝 构造来创建对象的时候,先前来管理空间的对象就不能在对空间进行任何的操作了,而我们在实际的应用之中,在赋值
之后,原来的对象还是需要对空间进行访问的,此时我们的AutoPtr类就没有什么太大的作用了。
 此时,我们就需要对AutoPtr类进行一定改造。
我们改造的方法就是在类中增加对于空间管理的标识,具体如下:
template<class T>
class AutoPtr
{
private:
	T* _ptr;
	mutable bool _owner;//bool类型的管理标识,如果是true就是可以对空间进行管理,如果是false,就表示不能对空间进行管理
public:
	//构造函数
	AutoPtr(T* ptr = NULL):_ptr(ptr),_owner(false)//默认把管理权限给成false,然后在函数体中进行判断,防止ptr是空
	{
		if(_ptr)//如果此时_ptr管理了一段空间,就把管理权限给成true
		{
			_owner = true;
		}
	}
	//拷贝构造函数
	//在初始化列表中,首先将ap的管理权交给当前对象
	AutoPtr(const AutoPtr<T>& ap):_ptr(ap._ptr),_owner(ap._owner)
	{
		ap._owner = false;//在ap将管理权给当前对象之后,那么ap就是失去了对空间的管理权,就将管理权改成false
	}
	//赋值运算符重载
	AutoPtr<T>& operator=(const AutoPtr<T>& ap)
	{
		if (this != &ap)//先判断是不是自己给自己赋值
		{
			if(_ptr && _owner)//如果当前对象有掌管空间,且对空间有管理的权利,在去把空间销毁掉
			{
				delete _ptr;
				_ptr = NULL;
			}
			_ptr = ap._ptr;//把ap的空间交给当前对象来管理
			_owner = ap._owner;//把ap的管理权交给当前对象来管理
			ap._owner = false;//因为ap把管理权交给当前对象来管理,那么ap就失去了管理这段空间的全力,所                  以把ap._owner改成false
		}
		return *this;
	}
	//析构函数
	~AutoPtr()
	{
		if(_ptr && _owner)//如果当前对象掌管空间且对这段空间且对空间拥有管理权,在去释放
		{
			delete _ptr;
			_ptr = NULL;
			_owner = false;
		}
	}
};
在我们增加了空间的管理标致之后,那么在赋值拷贝之后,之前的对象还是可以对空间进行访问,但是此时就会有一个疑问,
为什么我们不用深拷贝的方式来解决这种问题呢?因为此时,我们的空间是类外给出,也就是用户给的,在类内并不能自主的
给出空间,如果我们在类内自主的给出空间,就违背了原则,所以我们才用,管理标致的方法来解决。

 以上就是我们的AutoPtr,但是除了给出管理标记之外,我们还有还可以从根本来解决,就是一个对象只管理一段空间,
此时就是我们另外的一种智能指针: ScopedPtr
   ScopedPtr的目的就是防止拷贝,一个对象只管理一段空间,我们给出ScopedPtr的代码,在代码中讲解:
template<class T>
class ScopedPtr
{
private:
	T* _ptr;
public:
	//构造函数
	ScopedPtr(const T* ptr = NULL):_ptr(ptr)
	{}
	//析构函数
	~ScopedPtr()
	{
		if(_ptr)
		{
			delete _ptr;
		}
	}
	//运算符重载
	T& operator*()
	{
		return *_ptr;
	}
	//运算符重载
	T* operator->()
	{
		return _ptr;
	}
private:
	//拷贝构造
	//我们在类中把拷贝构造函数和赋值运算符声明成私有的,但是我们不给定义
	//根据类的性质,在类外是无法访问类内的成员的,此时就不能通过旧的对象给新的对象赋值了,也就实现了一个对象只掌管了一段对象
	ScopedPtr(const ScopedPtr<T>& sp);
	ScopedPtr& operator=(const ScopedPtr<T>& sp);
};

 对于把拷贝构造函数和赋值运算符声明为私有的是因为,如果只是声明不给定义,但函数仍然是公有性质的,那么如果用户在

类外给出定义,那么就会把类的封装性给破坏掉,此时我们在类中只给声明,不给定义,并且把函数的性质给成私有的,那么就实现了一个类只掌管了一段空间,当用旧的对象来构造新的对象的时候,就会出错。此时我们的ScopedPtr就实现了。
 但是之前我们写的智能指针,智能管理一个单个的对象,如果我们管理多个空间呢?此时在我们只需在原版的ScopedPtr改动
一点就可以,现在我们给出代码,和之前的SopedePtr区别就在于析构函数和运算符的重载函数的稍微不同:
template<class T>
class ScopedArray
{
private:
	T* _ptr;
public:
	ScopedArray(T* ptr):_ptr(ptr){}
	~ScopedArray()
	{
		if(_ptr)
			delete _ptr;
	}
	T& operator[](size_t index)
	{
		return _ptr[index];
	}
	const T& operator[](size_t index)
	{
		return _ptr[index];
	}
private:
	ScopedArray(const ScopedPtr<T>& sa);
	ScopedArray& operator=(const ScopedArray<T>& sa);
};

  现在,表面上看起来,ScopedPtr已经完善了,但是里面还是有一些的缺陷,因为类外在给出空间的时候,我们不能 确定是用new 给出的,还是用malloc给出,过着打开的是一个文件的指针,因为这三种方法对应的是不同的删除的方法,此时
如果我们只用一种delete来删除,就会出现错误。那么我们此时的改进的方法就是定制一个删除器,以此来给出对应的删除方法。
  我们定制删除器有两种方法,一种就是用函数指针,另外一种就是模版参数
现在我们讲一下使用函数指针的方法,首先我们先给出删除器:
//用来删除new开辟的空间
template<class T>
void Delete(T* p)
{
	if(p)
	{
		Delete( p;)
	}
}

//用来删除malloc开辟的空间
template<class T>
void Free(T* p)
{
	if(p)
	{
		free(p);
	}
}

//用来关闭文件指针
void Fclose(FILE* p)
{
	if(p)
	{
		fclose(p);
	}
}
 Tip:因为文件指针是固定的,所以就没有给成模版

此时我们的ScopedPtr类也应该改:

typedef void(*PFD)(void*);//我们把没有返回值,参数接收void*类型的函数重定义为PFD
template<class T>
class ScopedPtr
{
private:
	T* _ptr;
	PFD _pFDestory;//在对象中加入删除器类型的函数指针
public:
	//构造函数
	//默认把删除器类型给成销毁new类型空间
	ScopedPtr( T* ptr = NULL,PFD defDestory= Delete<T>):_ptr(ptr),_pFDestory(defDestory)
	{}
	//析构函数
	~ScopedPtr()
	{
		if(_ptr)
		{
			_pFDestory(_ptr);
		}
	}
	//运算符重载
	T& operator*()
	{
		return *_ptr;
	}
	//运算符重载
	T* operator->()
	{
		return _ptr;
	}
private:
	ScopedPtr(const ScopedPtr<T>& sp);
	ScopedPtr& operator=(const ScopedPtr<T>& sp);
};
 那么此时我们在析构函数删除开辟空间的时候,就会调用对应的删除器,就不会出现错误了。
 另外的一种办法就是:用模版的参数
我们把之前给出的删除器改成类模版:
//用来删除new开辟的空间
template<class T>
class DefDes
{
public:
	void operator()(T* p)//重载的符号是()
	{
		if(p)
			delete p;
	}
};

//用来删除malloc开辟的空间
template<class T>
class Free{
public:
	void operator()(T* p)
	{
		if(p)
			free(p);
	}
};

//用来关闭文件指针
class Fclose
{
public:
	void operator()(FILE* p)
	{
		if(p)
		{
			fclose(p);
		}
	}
};
在模版删除器中,我们使用了仿函数,在模版类中把()进行了重载,那么我们在使用的时候,就可以用无名对象来调用。
下面是使用模版参数来删除对用空间的ScopedPtr类:
template<class T, class Dp = DefDes<T> >
class ScopedPtr
{
private:
	T* _ptr;
public:
	//构造函数
	//默认把删除器类型给成销毁new类型空间
	ScopedPtr( T* ptr = NULL):_ptr(ptr)
	{}
	//析构函数
	~ScopedPtr()
	{
		if(_ptr)
		{
			Dp()(_ptr);//用Dp创建一个无名对象,调用重载之后的()
		}
	}
	//运算符重载
	T& operator*()
	{
		return *_ptr;
	}
	//运算符重载
	T* operator->()
	{
		return _ptr;
	}
private:
	ScopedPtr(const ScopedPtr<T>& sp);
	ScopedPtr& operator=(const ScopedPtr<T>& sp);
};
 此时我们使用模版参数来用删除器的ScopedPtr类就完成了。

Tip:限于编者水平,文章难免有缺漏之处,欢迎指正!
 如需转载请注明出处~
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值