首先,我要声明auto_ptr是一个坑!auto_ptr是一个坑!auto_ptr是一个坑!重要的事情说三遍!!!
通过上文,我们知道智能指针通过对象管理指针,在构造对象时完成资源的分配及初始化,在析构对象时完成资源的清理及汕尾工作.
因此,可以得到一份简洁版的智能指针代码:
template<typename T> class AutoPtr{ public: //构造函数,完成资源的初始化与分配 AutoPtr(T * ptr = NULL) :_ptr(ptr){} //析构函数,完成资源的清理及汕尾工作 ~AutoPtr(){ if(_ptr!=NULL){ delete _ptr; _ptr = NULL; } } private: T *_ptr; };
大致一看,没毛病!突然觉得自己无所不能,感觉自己就是传说中的编程天才!
可是,如果我想这样的话.....:
AutoPtr<int> ap1(new int(100)); AutoPtr<int> ap2(new int(200)); AutoPtr<int> ap3(ap1); AutoPtr<int> ap4(); ap4 = ap2;
ap1与ap3共同管理一块空间;ap2与ap4共同管理一块空间,看起来好像没什么问题.
但当程序跑起来,出了函数作用域之后....崩毁了!!?
......Why?
好在我经验丰富,见多识广,脑袋回路中很自然地想起了类似的情况:string类的浅拷贝....
因此,机智的我立刻发现了原因:由于没有定义拷贝构造函数与赋值运算符重载,那么在拷贝构造对象和给对象赋值时,系统会默认生成相应函数.
ap1与ap3共同管理一块空间,一旦出了函数作用域,ap3会调用析构函数,delete掉所指向的空间;
而当ap1调用析构函数时,此时ap1所指向的已经是一块非法内存(因为被ap3 delete过了),因此当ap1再次delete这块空间时,程序挂掉了!
简而言之:同样一块空间被delete了两次,所以最终程序挂掉了!
那么我就好奇了,auto_ptr如何应对拷贝与赋值的呢?
在百度了各种资料及阅读其源代码之后,发现auto_ptr是这么处理的:
//拷贝构造 AutoPtr(AutoPtr& ap){ //转移管理权 _ptr = ap._ptr; ap._ptr = NULL; } //赋值运算符重载 AutoPtr &operator=(AutoPtr &ap){ if(ap._ptr != _ptr){ AutoPtr tmp(ap); std::swap(_ptr,tmp._ptr); }//由析构函数去管理tmp return *this; }
这是我简化后的代码,再次应对上述情况时:
AutoPtr<int> ap1(new int(100)); AutoPtr<int> ap2(new int(200)); AutoPtr<int> ap3(ap1); //ap3 = NULL AutoPtr<int> ap4(); ap4 = ap2; //ap2 = NULL
我们发现:auto_ptr通过转移管理权,来保证在赋值与拷贝时仅管理一份指针,而防止同一块空间释放多次的问题.
最后,我将自己写的简洁、精简、易读的AutoPtr与库中的代码一起贴上来
/* *文件说明:智能指针之AutoPtr *作者:高小调 *日期:2017-03-30 *集成开发环境:Microsoft Visual Studio 2010 *Github:https://github.com/gaoxiaodiao/c_cplusplus/blob/master/SmartPointer/AutoPtr.h */ #pragma once template<typename T> class AutoPtr{ public: //构造函数 AutoPtr(T * ptr = NULL) :_ptr(ptr){} //拷贝构造 AutoPtr(AutoPtr& ap){ //转移管理权 _ptr = ap._ptr; ap._ptr = NULL; } //赋值运算符重载 AutoPtr &operator=(AutoPtr &ap){ if(ap._ptr != _ptr){ AutoPtr tmp(ap); std::swap(_ptr,tmp._ptr); }//由析构函数去管理tmp return *this; } //析构函数 ~AutoPtr(){ if(_ptr!=NULL){ delete _ptr; _ptr = NULL; } } private: T *_ptr; }; void TestAutoPtr(){ AutoPtr<int> ap1(new int(100)); AutoPtr<int> ap2(new int(200)); AutoPtr<int> ap3(ap1); AutoPtr<int> ap4(ap2); ap3 = ap4; }
库内实现(我就懒得写注释了,看完精简版后,再看库中实现会发现库内的封装性、代码复用性更高一些)
template<class T> class auto_ptr { private: T*ap; public: //constructor & destructor-----------------------------------(1) explicit auto_ptr(T*ptr=0)throw():ap(ptr) { } ~auto_ptr()throw() { delete ap; } //Copy & assignment--------------------------------------------(2) auto_ptr(auto_ptr& rhs)throw():ap(rhs.release()) { } template<class Y> auto_ptr(auto_ptr<Y>&rhs)throw():ap(rhs.release()) { } auto_ptr& operator=(auto_ptr&rhs)throw() { reset(rhs.release()); return *this; } template<class Y> auto_ptr& operator=(auto_ptr<Y>&rhs)throw() { reset(rhs.release()); return *this; } //Dereference----------------------------------------------------(3) T& operator*()const throw() { return *ap; } T* operator->()const throw() { return ap; } //Helper functions------------------------------------------------(4) //value access T* get()const throw() { return ap; } //release owner ship T* release()throw() { T*tmp(ap); ap=0; return tmp; } //reset value void reset(T*ptr=0)throw() { if(ap!=ptr) { deleteap; ap=ptr; } } //Special conversions-----------------------------------------------(5) template<class Y> struct auto_ptr_ref { Y*yp; auto_ptr_ref(Y*rhs):yp(rhs){} }; auto_ptr(auto_ptr_ref<T>rhs)throw():ap(rhs.yp) { } auto_ptr& operator=(auto_ptr_ref<T>rhs)throw() { reset(rhs.yp); return*this; } template<class Y> operator auto_ptr_ref<Y>()throw() { returnauto_ptr_ref<Y>(release()); } template<class Y> operator auto_ptr<Y>()throw() { returnauto_ptr<Y>(release()); } };
与君共勉!