void FunTest()
{
int *p = new int[10];
FILE* fp = fopen("1.txt","rb");
if(fp==NULL)
{
return;//因为忘记释放p,所以会造成内存的泄漏
}
//Dosomething;
if(p!=NULL)
{
delete[] p;//若文件打开成功,则会忘记释放fp;
p = NULL;
}
}
如上,我们一不小心就会忘记释放我们开辟的空间或者文件,造成内存泄漏的问题,为了解决上述的问题,我们引入智能指针的概念
什么是智能指针?
智能指针,RAII,即资源的分配及初始化,定义一个类来封装资源的分配及释放,在构造函数中完成资源的分配和初始化,在析构函数中完成资源的清理。
智能指针的目的是实现资源的管理,有三种方法来进行资源的管理
- 资源转移— auto_ptr
- 防拷贝—scoed_ptr
引用计数—shared_ptr
方法一:资源的转移,即auto_ptr,有三个版本的实现,每个版本都出现了一些问题,所以auto_ptr我们是不用的,但这里我们列出三个版本,来理解auto_ptr智能指针的思想
为了管理资源,我们创建一个类,来进行资源的管理
template<class T>
class autoptr
{
public:
autoptr(T* ptr = NULL)//构造函数
:_ptr(ptr)
{}
//拷贝构造函数
autoptr(const autoptr<T>& ap)//ap2(ap1),两个指针指向同一块区域,在析构时对同一块空间析构两次,程序崩溃
:_ptr(ap._ptr)
{}
//赋值运算符的重载
autoptr<int>& operator=(const autoptr<T>& ap)//ap3 = ap1,原因同上,对同一块空间释放了两次
{
if(this!=&ap)
{
if(_ptr!=NULL)
{
delete _ptr;
}
_ptr = ap._ptr;
}
return *this;
}
~autoptr()//析构函数
{
if(_ptr!=NULL)
{
delete _ptr;
}
}
//->的重载
T* operator->()const
{
return _ptr;
}
//*的重载
T& operator*()const
{
return *_ptr;
}
//得到原生态指针
T* Get()const
{
return _ptr;
}
private:
T* _ptr;
};
void funtest()
{
autoptr<int> ap1(new int);//ap1创建成功
//autoptr<int> ap2(ap1);//调用拷贝构造函数,程序崩溃
autoptr<int> ap3(new int);
ap3 = ap1;//调用赋值运算符重载,程序崩溃
}
int main()
{
funtest();
getchar();
return 0;
}
为了解决上述的问题,推出第一个版本的auto_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;
}
~autoptr()//析构函数
{
if(_ptr!=NULL)
{
delete _ptr;
}
}
//->的重载
T* operator->()const
{
return _ptr;
}
//*的重载
T& operator*()const
{
return *_ptr;
}
//得到原生态指针
T* Get()const
{
return _ptr;
}
private:
T* _ptr;
};
//产生的问题
// 问题一: 资源转移后,ap1,ap2再不能访问空间了
//问题二:const类型的指针,再不能由它拷贝别的指针了
//问题三:当智能指针做函数实参传递时,则该智能指针在函数中再不能访问
void funtest()
{
int* p = new int;
autoptr<int> ap1(p);//成功创建了智能指针ap1;
autoptr<int> ap2(ap1);//成功调用拷贝构造函数创建了智能指针ap2,
autoptr<int> ap3(new int);
ap3 = ap2;//成功调用赋值运算符重载函数将ap1赋给 ap2;
//解决了上诉的调用拷贝构造函数和赋值运算符重载时的对同一块空间析构两次的问题,但是也引入了新的问题
// 问题一: 资源转移后,ap1,ap2再不能访问空间了
//*ap1 = 10;
//*ap2 = 20;//对NULL截引用,程序崩溃
//问题二:const类型的指针,再不能由它拷贝别的指针了
const autoptr<int> ap4(new int);
//autoptr<int> ap5(ap4);//因为const修饰智能指针对象ap4后,ap4再不能更改,在拷贝构造函数时执行ap4._ptr = NULL出错
}
void funtest1(autoptr<int> ap)//值传递,有一份临时拷贝
{}
void funtest2(autoptr<int>& ap)//引用,则不会产生临时拷贝,指针可以继续使用
{}
int main()
{
funtest();
//问题三:当智能指针做函数实参传递时,则该智能指针在函数中再不能访问
autoptr<int> ap5(new int);
funtest1(ap5);//值传递
funtest2(ap5);//传引用
*ap5 = 10;
getchar();
return 0;
}
第一个版本的auto_ptr指针存在上面三个问题,为了解决上面的问题,我们推导出auto_ptr的第二个版本
template<class T>
class autoptr
{
public:
autoptr(T* ptr = NULL)
:_ptr(ptr)
,_owner(false)
{
if(ptr!=NULL)
{
_owner = true;
}
}
autoptr(const autoptr<T>& ap)
:_ptr(ap._ptr)
,_owner(ap._owner)
{
ap._owner = false;
}
autoptr<T>& operator=(const autoptr<T>& ap)
{
if(this!=&ap)
{
if(_ptr && _owner)
{
delete _ptr;
_ptr = ap._ptr;
}
_owner = true;
ap._owner = false;
}
return *this;
}
~autoptr()
{
if(_ptr!=NULL)
{
if(_owner == true)
{
delete _ptr;
_ptr = NULL;
_owner = false;
}
}
}
T* operator->()const
{
return _ptr;
}
T& operator*()const
{
return *_ptr;
}
T* Get()const
{
return _ptr;
}
private:
T* _ptr;
mutable bool _owner;//mutable关键字,可变的,即使是const修饰的类对象,mutable修饰的类成员也是可以改变的
};
void funtest()
{
autoptr<int> ap1(new int);
autoptr<int> ap2(ap1);
autoptr<int> ap3(new int);
ap3 = ap2;
*ap2 = 10;
*ap3 = 20;//解决了上诉的问题一,即资源转移后,ap1,ap2任然可以访问空间
const autoptr<int> ap4(new int);
autoptr<int> ap5(ap4);//解决了上述的问题二,即const类型的指针,可以由它拷贝别的指针
*ap4 = 100;
}
//虽然解决了问题一和问题二,但是产生了新的问题,ap1,ap2,ap4成为了野指针,只释放了ap5.
void funtest1(autoptr<int> ap)//不能解决问题三
{}
int main()
{
funtest();
//问题三:当智能指针做函数实参传递时,则该智能指针在函数中再不能访问
autoptr<int> ap5(new int);
funtest1(ap5);//不能解决的原因是当拷贝完成后,ap5._owner=false,当main函数调用完,析构函数不能析构ap5,成为野指针,
//但不会造成内存泄漏,因为ap释放了ap5的空间。
*ap5 = 10;//野指针的问题
getchar();
return 0;
}
第二个版本的auto_ptr解决了上述的问题一与问题二,但是又重新添加了一个野指针的问题
当我们用智能指针管理一个无名的智能指针对象时,编译器会自动优化,调用系统的构造函数,如果我们想调用我们自己写的构造函数,我们推导出智能指针的第三个版本
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& 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();
getchar();
return 0;
}
在这三个auto_ptr智能指针的版本中,都存在无法解决的问题,所以我们在管理资源时,最好不要用auto_ptr来进行资源的转移来管理资源