智能指针就是智能,自动化地管理指针所指向的动态资源的释放,那仫既然已经有了指针的概念那仫为什仫还会有智能指针呢?请看下面一种场景
void fun() { int *ptr=new int(1); if(1) throw 1; delete ptr; } void test1() { try { fun(); } catch(...) { cout<<"未知异常"<<endl; } }
在上述场景中我们发现如果在程序中出现异常进而抛出异常,会出现内存泄漏的问题(ptr无法释放),此时我们就需要一个能够出作用域就自动释放的内存空间的指针,而智能指针的提出就是为了解决异常安全的问题;智能指针其实并不是指针,它一个类,但是它却做着和指针类似的事儿:在构造函数中完成资源的分配和初始化,在析构函数中完成资源的清理,保证资源的正确初始化和释放.这也就提出了一个和智能指针类似的概念-RAII(Resource Acquisition Initialization),资源分配及初始化,定义一个类来封装资源的分配和释放
auto_ptr的模拟实现
template<typename T> class AutoPtr { public: AutoPtr(T *ptr) :_ptr(ptr) { cout<<"AutoPtr()"<<endl; } AutoPtr(AutoPtr<T>& ap) //权限转移 { _ptr=ap._ptr; ap._ptr=0; } AutoPtr<T>& operator=(AutoPtr<T>& ap) { if(_ptr != ap._ptr) { delete _ptr; _ptr=ap._ptr; ap._ptr=0; } return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } void Reset(T *ptr) //重置 { delete _ptr; _ptr=ptr; } ~AutoPtr() { if(_ptr != 0) { cout<<"~AutoPtr()"<<endl; delete _ptr; _ptr=0; } } private: T* _ptr; //bool owner; //不安全 };
auto_ptr是通过权限转移的方法解决多个指针指向同一块空间而该空间不会被释放多次的问题的.所谓的权限转移主要体现在拷贝构造和赋值操作符重载上,权限转移其实就是当需要以一个已有的对象初始化另一个对象或者需要给有一个对象赋值时我们可以使得维护该对象的智能指针为空,而另一个维护该对象的智能指针指向该空间.
scoped_ptr的模拟实现
template<typename T> class ScopedPtr { public: ScopedPtr(T *ptr) :_ptr(ptr) {} T& operator*() { return *_ptr; } T* operator->() { return _ptr; } ~ScopedPtr() { if(_ptr != 0) { delete _ptr; _ptr=0; } } protected: ScopedPtr(ScopedPtr<T>& sp); ScopedPtr<T>& operator=(ScopedPtr<T>& ap); private: T *_ptr; };
scoped_ptr是一个防拷贝,防赋值的智能指针,所以scoped_ptr的拷贝构造函数和赋值操作符重载可声明为private或者protected即可不需实现,但是不可声明为public.因为如果申明为public不实现,虽然在该类域中达到了防拷贝,防赋值的效果,但是如果继承之后实现了呢?此时依然可以在继承类中拷贝构造和赋值,所以不可声明为public.
shared_ptr的模拟实现
template<typename T> class SharedPtr { public: SharedPtr(T *ptr) :_ptr(ptr) ,_pcount(new int(1)) { cout<<"构造"<<endl; } SharedPtr(const SharedPtr<T>& sp) :_ptr(sp._ptr) ,_pcount(sp._pcount) { cout<<"拷贝构造"<<endl; ++(*_pcount); } SharedPtr<T>& operator=(SharedPtr<T>& sp) { cout<<"赋值操作符"<<endl; if(_ptr != sp._ptr) { if(--(*_pcount) == 0) { delete _ptr; delete _pcount; } _ptr=sp._ptr; _pcount=sp._ptr; ++(*_pcount); } return *this; } T* operator->() { return _ptr; } T& operator*() { return *_ptr; } T* Get() { return _ptr; } int GetCount() { return *_pcount; } ~SharedPtr() { cout<<"析构了"<<endl; if(--(*_pcount) == 0) { cout<<"删除了"<<endl; delete _ptr; _ptr=0; delete _pcount; _pcount=0; } } private: T *_ptr; int *_pcount; };
shared_ptr解决多个指针指向同一块空间会被析构多次这个问题的方法是引用计数,
在shared_ptr的模拟实现中设置了一个pcount指针用来记录该空间被多少个指针所指向,当然在释放该空间时只要该pcount不为0则只需要将该pcount空间的内容减减就可以了.是不是觉得shared_ptr很厉害呢?实际上当使用智能指针时最常用的就是shared_ptr,但是它也存在问题-循环引用的问题
请看下面一种场景:
struct Node { ~Node() { cout<<"~Node()"<<endl; } int _data; boost::shared_ptr<Node> _next; boost::shared_ptr<Node> _prev; }; void testshared_ptr() //循环引用的问题 { boost::shared_ptr<Node> sp1(new Node); boost::shared_ptr<Node> sp2(new Node); cout<<sp1.use_count()<<endl; //1 cout<<sp2.use_count()<<endl; //1 sp1->_next=sp2; sp2->_prev=sp1; cout<<sp1.use_count()<<endl; //2 cout<<sp2.use_count()<<endl; //2 }
在定义双向结点结构时,我们知道sp1,sp2,_next,_prev都是shared_ptr类型的智能指针,我们把这两块空间称为n1,n2吧!sp1和sp2的_prev都指向n1,sp2和sp1的_next都指向n2,我们知道shared_ptr是采用引用计数的方法,那仫既然有两个相同类型的智能指针都指向同一块空间,而且空间n1,n2都是由两个相同类型的智能指针指向的,那仫这两个空间的引用计数器的内容都为2,那仫此时又会出现什仫问题呢?
如上图所示,当销毁该空间时会出现内存泄漏的问题,因为该块空间n1分别是由两个同类型的指针维护的,当销毁n1时需要n2的引用计数降下来,而销毁n2时需要n1的引用计数降下来,这就导致了无限循环的问题,这种场景让我想到了踢皮球游戏:你踢给我,我踢给你,最后谁也得不到了.当然到最后这两块空间都是无法释放的.为了解决shared_ptr中的循环引用问题,又引入了一个新的智能指针-weak_ptr
struct Node { ~Node() { cout<<"~Node()"<<endl; } int _data; boost::weak_ptr<Node> _next; boost::weak_ptr<Node> _prev; }; void testshared_ptr() //循环引用的问题 { boost::shared_ptr<Node> sp1(new Node); boost::shared_ptr<Node> sp2(new Node); cout<<sp1.use_count()<<endl; //1 cout<<sp2.use_count()<<endl; //1 sp1->_next=sp2; sp2->_prev=sp1; cout<<sp1.use_count()<<endl; //1 cout<<sp2.use_count()<<endl; //1 }
weak_ptr是弱指针,它使得该引用计数不会增长;而且weak_ptr不可以单独使用,必须与shared_ptr配套使用,因为weak_ptr的引入就是为了解决shared_ptr循环引用的问题
shared_ptr还存在线程安全的问题,有兴趣的童鞋可参考http://blog.csdn.net/jiangfuqiang/article/details/8292906