在介绍智能指针之前我们先看以下代码:
void FunTest()
{
int *p = new int[10];
FILE* pFile = fopen( "1.txt", "w" );
if (pFile == NULL)
{
return;
}
// DoSomethint();
if (p != NULL)
{
delete[] p;
p = NULL;
}
}
在该代码中,我们注意了对指针的释放却忽略了对文件指针的关闭。C++中的动态内存需要用户自己来维护,动态开辟的空间,在出函数作用域或者程序正常退出前必须释放掉,否则会造成内存泄露,有时我们已经非常谨慎了,然防不胜防。
智能指针(RAII)的存在一开始就是为了解决资源分配即初始化,其解决方案是定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
所以有了第一代Auto_Ptr
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)
:_ptr(ptr)
{}
AutoPtr(AutoPtr<T>& s)
:_ptr(s._ptr)
{
s._ptr = NULL;
//经过赋值拷贝,原本的智能指针s中_ptr 已经指向空
}
AutoPtr<T>& operator=(AutoPtr<T>& s)
{
if (this != &s)
{
if (_ptr)
{
delete _ptr;
}
_ptr = s._ptr;
s._ptr = NULL;
//经过赋值运算符后同样被置空
}
return *this;
}
T* operator->()const
{
return _ptr;
}
T& operator*()const
{
return *_ptr;
}
~AutoPtr()
{
if (_ptr)
{
delete _ptr;
}
}
private:
T* _ptr;
};
void Funtest1(AutoPtr<char> p)
{}
void Funtest2(AutoPtr<char>& p)
{}
int main()
{
AutoPtr<char> p1(new char);
AutoPtr<char> p2(p1);
//AutoPtr<char> p4(p3); 出错,不能通过编译,原因在拷贝构造函数中更改了具有常性的 p3
//Funtest1(p2); 同理,以传值形式作为函数形参,也要经过拷贝构造函数形成临时变量p,同理赋值崩溃
Funtest2(p2); //以引用的方式传递没有问题
*p2 = '1';
return 0;
}
由此可见一代Auto_Ptr的缺点都有以下几点:
1:在Auto_Ptr中经过资源转移后,p1不能再访问空间
2:以值传递的形式作为函数参数后,一旦调用函数,该智能指针在原函数中也不能访问空间
3:const对象不能构造同类型对象
针对第一代Auto_Ptr的问题,于是乎有了第二代Auto_Ptr的产生实际上我们是使用一个bool 类型的 数据成员来表明该智能指针是否具有被析构的能力
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)
:_ptr(ptr)
, type(false)
{
if (_ptr)
type = true;
}
AutoPtr(const AutoPtr<T>& s)
:_ptr(s._ptr)
, type(s.type)
{
s.type = false;
}
//拷贝构造函数中,将s对象具有析构的能力 type赋给新对象,而s.type赋值为false,说明不具有析构能力
AutoPtr<T>& operator=(const AutoPtr<T>& s)
{
if (this != &s)
{
if (_ptr && type)
{
delete _ptr;
}
_ptr = s._ptr;
type = s.type;
s.type = false;
}
return *this;
}
T& operator*()const
{
return *_ptr;
}
//析构函数中,我们将 type 为true的智能指针进行析构,从而避开了对同一空间释放多次的问题
~AutoPtr()
{
if (_ptr && type)
{
delete _ptr;
_ptr = NULL;
type = false;
}
}
private:
T* _ptr;
mutable bool type;
//在成员函数体内只改变了type的值,针对于const对象,我们加关键字mutable可以更改type的值
};
void Funtest1(AutoPtr<char> p)
{}
int main()
{
AutoPtr<char> p1(new char);
void Funtest1(p1);
AutoPtr<char> p2(p1);
AutoPtr<char> p3(new char);
AutoPtr<char> p4(p3);
Funtest1(p1);
p1 = p3;
*p3 ='1';
return 0;
}
经过验证,我们确定基于第一代的修改我们成功的引入了第二代,但是我们引入了一个野指针的问题,一旦对于野指针进行操作,很容易造成问题,
当我们用智能指针管理一个无名的智能指针对象时,编译器会自动优化,调用系统的构造函数,如果我们想调用我们自己写的构造函数,我们推导出智能指针的第三个版本
template<class T>
class autoptrref
{
public:
autoptrref(T* ptr = NULL)
:_ptr(ptr)
{}
T* _ptr;
};
template<class T>
class autoptr
{
public:
//构造函数
autoptr(T* ptr = NULL)
:_ptr(ptr)
{}
//拷贝构造函数
autoptr(autoptr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = NULL;//拷贝完后将ap._ptr置NULL
}
//赋值运算符的重载
autoptr<int>& operator=(autoptr<T>& ap)
{
if (this != &ap)
{
if (_ptr != NULL)
{
delete _ptr;
}
_ptr = ap._ptr;
ap._ptr = NULL;//赋值完后将ap._ptr置NULL
}
return *this;
}
//解决办法
//重载autoptrref()
operator autoptrref<T>()//将autoptr<T>转化为autoptrref<T>
{
autoptrref<T> temp(_ptr);
_ptr = NULL;
return temp;
}
//再重载一个拷贝构造函数
autoptr(const autoptrref<T>& apr)
:_ptr(apr._ptr)
{}
~autoptr()//析构函数
{
if (_ptr != NULL)
{
delete _ptr;
}
}
//*的重载
T& operator*()const
{
return *_ptr;
}
//得到原生态指针
T* Get()const
{
return _ptr;
}
private:
T* _ptr;
};
void funtest()
{
autoptr<int> ap(autoptrref<int>(autoptr<int>(new int)));
//用无名的智能指针拷贝构造智能指针ap,则不会调用我们编写好得拷贝构造函数,而是调用系统自动合成个的拷贝构造函数
} //原因是编译器的自动优化,解决办法:重新增加一个类autoptrref,将无名对象的类型转化为autoptrref
int main()
{
funtest();
return 0;
}