一、有关智能指针的基本信息
(一)智能指针是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; }