一、有关智能指针的基本信息
(一)智能指针是RAII思想的一个产品
RAII(资源获得即初始化),定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
所谓智能指针,就是智能化(自动化)管理指针所指向的动态资源的释放。
(二)为什么要有智能指针
eg1:
void Test1()
{
int* p=new int(1);
if(1)
{
return;
}
delete p; //未执行
}
eg2:
void DoSomeTing()
{
if(1)
{
throw 1;
}
}
void Test2()
{
int* p1=new int(2);
DoSomeTing();
delete p1; //未执行
}
int main()
{
try
{
Test2();
}
catch(...)
{
cout<<"未知异常"<<endl;
}
return 0;
}
要解决此类问题,就需要使用智能指针。
二、智能指针
Boost库中的智能指针:
- 管理权转移(带缺陷的设计)——>auto_ptr
- 防拷贝(简单粗暴)——>scoped_ptr
- 引用计数(更实用更复杂)——>shared_ptr
C++11库中引入了unique_ptr(相当于scoped_ptr)、shared_ptr和weak_ptr。
(一)auto_ptr
先实现一个最简单的智能指针AutoPtr:
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{}
~AutoPtr()
{
if(_ptr)
{
delete _ptr;
_ptr=NULL;
}
}
protected:
T* _ptr;
};
利用该智能指针,可以将上述的eg1和eg2改正为:
void Test1()
{
int* p=new int(1);
AutoPtr<int> ap(p);
if(1)
{
return;
}
}
void DoSomeTing()
{
if(1)
{
throw 1;
}
}
void Test2()
{
int* p1=new int(2);
AutoPtr<int> ap(p1);
DoSomeTing();
}
int main()
{
try
{
Test1();
Test2();
}
catch(...)
{
cout<<"未知异常"<<endl;
}
return 0;
}
但这种智能指针是存在缺陷的。
如果将main函数换为下述内容,则会使程序崩溃。
int main()
{
AutoPtr<int> ap1(new int(1));
AutoPtr<int> ap2(ap1);
return 0;
}
这是因为ap1和ap2两个对象指向了同一个地方,导致该地方要被析构两次。
需要加入管理权转移的功能(加入拷贝构造函数如下):
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr=NULL;
}
//此时属于浅拷贝,ap2(ap1)时,转移后ap1被置空,再使用时程序崩溃。说明此时智能指针无法完全实现指针的作用。
//至于该构造没有使用const,是因为_ptr不是const型的,因而设为const类型后无法赋值。
//另外,一般传引用时使用const是不希望改变原来的,但这里需要修改,所以不加const
//(此时使用深拷贝也不可以,因为开辟两块空间的话就是两个智能指针,而重载的目的是希望它是一个指针。)
最后,为了完整实现AutoPtr,还需要加入运算符重载函数:
AutoPtr<T>& operator=(AutoPtr<T>& ap) //赋值运算符重载
{
if (this != &ap)
{
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
T& operator*() //解引用操作符重载
{
return*_ptr;
}
T* operator->() //->操作符重载(管理的是结构体的智能指针时)
{
return_ptr;
}
Auto_ptr虽然能够通过管理权转移解决多对象指向同一地方的问题,但仍是一种有缺陷的智能指针,很多时候不允许使用。
(二)scoped_ptr
scoped_ptr的模拟实现如下:
template<class T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
~ScopedPtr()
{
if(_ptr)
{
cout<<"delete"<<_ptr<<endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
ScopedPtr(ScopedPtr<T>& sp); //只声明不实现
ScopedPtr<T>& operator=(ScopedPtr<T>& sp); //只声明不实现
prtected:
T* _ptr;
};
scoped_ptr为了防止拷贝,直接不允许拷贝,简单粗暴,但也不完美。
(三)shared_ptr
对于多对象指向同一地方的问题,前面已经介绍了管理权转移和防拷贝两种方法,而shared_ptr介绍了第三种解决方法——引入一个引用计数对象。
对此方法可依据下图进行理解:
shared_ptr的模拟实现如下:
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pCount(new long(1))
{}
~SharedPtr()
{
if (--(*_pCount) == 0)
{
delete _ptr;
delete _pCount;
}
}
SharedPtr(SharedPtr<T>& sp) //拷贝构造,需要注意使计数加1
:_ptr(sp._ptr)
, _pCount(sp._pCount)
{
++(*_pCount);
}
SharedPtr<T>& operator=(SharedPtr<T>&sp) //赋值运算符的重载
{
return*this;
}
T& operator*() //重载*
{
return*_ptr;
}
T* operator->() //重载->
{
return_ptr;
}
longUseCount() //返回引用计数的值,方便查看,例如: cout<<"sp1:"<<sp1.UseCount()<<endl;
{
return*_pCount;
}
protected:
T* _ptr;
long* _pCount;
};
void TestSharedPtr()
{
SharedPtr<int> sp1(new int(1));
SharedPtr<int> sp2(sp1);
}
int main()
{
TestSharedPtr();
system("pause");
return 0;
}
但对于赋值运算,其实有三种情况:
而上述的shared_ptr模拟实现代码只能解决前两种,需要对拷贝构造函数进行改进。
SharedPtr<T>& operator=(SharedPtr<T>&sp) //改进后的拷贝构造
{
if(_ptr != sp._ptr)
{
if(--(*_pCount) == 0)
{
delete_ptr;
delete_pCount;
}
_ptr =sp._ptr;
_pCount =sp._pCount;
++(*_pCount);
}
return*this;
}
void TestSharedPtr() //对上述三种情况进行测试,都可通过
{
SharedPtr<int> sp1(new int(1));
SharedPtr<int> sp2(sp1);
SharedPtr<int> sp3(sp2);
sp1 = sp1;
sp1 = sp2;
SharedPtr<int> sp4(new int(2));
sp1 = sp4;
}