一、auto_ptr的简单模拟实现
关于auto_ptr的缺陷我在之前的博客中就已经说明。在这篇博客中我们从它的底层来更进一步阐述。
在学习中,对某一个类进行简单的模拟,一般情况下,我们只需要模拟实现四个函数,分别是:构造函数、析构函数、拷贝构造函数以及赋值运算符重载。但是对于一个指针来说,它还要加上*
运算符重载和->
运算符重载
template<class T>
class Auto_ptr
{
public:
//构造函数
Auto_ptr(T* _ptr=NULL)
{
ptr = _ptr;
}
//析构函数
~Auto_ptr()
{
if (ptr != NULL)
{
delete ptr;
}
}
//拷贝构造函数
Auto_ptr(const Auto_ptr<T>& ap)
{
ptr = ap.ptr;
ap.ptr = NULL;
}
//赋值运算符重载
Auto_ptr<T>& operator=(Auto_ptr<T>& ap)
{
if (this != &ap)
{
if (ptr != NULL)
delete ptr;
ptr = ap.ptr;
ap.ptr = NULL;
}
return *this;
}
T& operator*()
{
return *ptr;
}
T* operator->()
{
return ptr;
}
private:
T * ptr;
};
二、对auto_ptr的缺陷的分析
从赋值运算符重载函数和拷贝构造函数来看,他们所使用的的是一个浅拷贝,所以如果auto_ptr对象之间出现了赋值和拷贝的情况,那么就会出现,多个指针指向了同一个空间的情况,在对象声明周期结束的时候,会出现同一块空间被多次释放的情况,所以为了改变这种情况,auto_ptr使用的是一个管理权转移的思想,所以在发生拷贝构造或者是赋值时,将原来的auto_ptr置为空,这样就解决了二次释放的问题。但是这样也有一个缺陷,那就是在用原来的auto_ptr访问数据时,会出现内存错误,因为此时的auto_ptr是一个NULL,用一个NULL去访问数据显然是C++不允许的。
三、对赋值运算符重载函数的一些说明
(1)赋值运算符重载
对于一个赋值运算符重载,我们有以下四点要注意的地方(注意这不仅仅是对于以上代码而言,对于所有的赋值运算符重载都适用):
-
检查返回值类型是否是该类类型的引用,并且返回值也是自身引用(即*this)。
因为只有返回一个引用类型,那么才能做到连续赋值(即s1=s2=s3),否则如果函数返回的不是一个引用,那么在进行连续赋值的时候,编译器就会报错。 -
检查函数的形参是否是const引用类型。
因为如果传入的参数不是引用,而是一个实例的话,那么在实参对形参结合的这个过程中会调用拷贝构造函数。而如果是一个引用就会避免这种无端的销毁,从而提高效率。之所以声明为const,是因为在赋值运算符中,我们可以保证不会对对象进行修改。 -
检查在赋值之前是否释放掉自身已有内存。
如果我们忘记在赋值之前释放掉自身已有内存,那么就会出现内存泄漏。 -
检查是否判断自我赋值
因为在赋值运算符重载函数中,我们首先要对自身对象进行释放,然后才开始赋值。如果没有判断自我赋值,那么当是同一个对象进行赋值时,释放掉自身对象,其实也是将传入参数的内存也释放掉了,因此就找不到要赋值的内容了。