RAII资源分配即初始化,定义一个类来封装资源的分配和释放,在构造 函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。

智能指针的引入:

由于return ,throw等关键字的存在,导致顺序执行流的错乱,不断的进行跳转,使开辟的空间

看似被释放,而实际上导致内存的泄露。

例如以下两个例子:

voidTest1(){int*p1=newint(1);if(1){return;}deletep1;}voidDoSomeThing(){//...throw2;//...}voidTest2(){int*p1=newint(2);//...try{DoSomeThing();}catch(...){//deletep1;throw;}//...deletep1;}

以上两个例子,看似new与delete结合使用,但是实际上已经导致内存的泄露,为了解决以上问题,就需要在写此类代码时需要多加小心,但是对于大量的代码开发,想要注意就很难了。

于是乎就引入了智能指针:

所谓智能指针就是智能/自动化的管理指针所指向的动态资源的释放。

c++库函数中的智能指针为(auto_ptr,unique_ptr,shared_ptr);

Boost库函数中的智能指针为(auto_ptr,scoped_ptr,shared_ptr)。


接下来就一起来了解一下智能指针的发展历史。

1、在vc6.0之前的版本中auto_ptr的模拟实现方式

template<classT>classOldAutoPtr{public:OldAutoPtr(T*ptr):_ptr(ptr),_owner(true){}//拷贝构造OldAutoPtr(OldAutoPtr&ap):_ptr(ap._ptr),_owner(ap._owner){ap._owner=false;//权限的转让}//运算符重载OldAutoPtr&operator=(OldAutoPtr&ap){if(ap._ptr!=_ptr)//自赋值和指向同一份空间{if(_ptr){delete_ptr;}_ptr=ap._ptr;_owner=ap._owner;//权限的转让ap._owner=false;}return*this;}//析构函数~OldAutoPtr(){if(_owner)//只删除拥有权限的指针{delete_ptr;}}public:T&operator*(){return*_ptr;}T*operator->(){return_ptr;}private:T*_ptr;bool_owner;//权限拥有者};


评价:

看似最初版本的auto_ptr已经很接近了原声指针的使用,多个指针都可以指向一份空间,而且释放的同时不会导致同一份空间的多次释放。但是在下面的情境中却发生了致命的危害:


voidTestOldAutoPtr(){OldAutoPtr<int>ap1(newint(1));if(true){OldAutoPtr<int>ap2(ap1);//OldAutoPtr<int>ap3(newint(2));//ap3=ap2;}//ap1将析构的权限给予了ap2,ap1,ap2指向同一份空间,ap2在出了if作用域之后ap2对象释放,进而导致ap1也被释放。//但是在if作用域之外,又对ap1(ap2)指向的空间进行简引用,导致程序崩溃,如果不使用的话则会造成指针的悬挂(野指针)。*ap1=10;}

针对以上的状况,在之后的版本中对auto_ptr进行了完全的权限转移,转移之后该指针不能使用。


2、现在的版本auto_ptr的实现

template<typenameT>classAutoPtr{public:AutoPtr(T*ptr):_ptr(ptr){}AutoPtr(AutoPtr<T>&ap):_ptr(ap._ptr){ap._ptr=NULL;//权限转移}AutoPtr<T>&operator=(AutoPtr<T>&ap){if(this!=&ap)//自赋值{if(_ptr){delete_ptr;}_ptr=ap._ptr;ap._ptr=NULL;//权限转移}return*this;}~AutoPtr(){if(_ptr!=NULL){delete_ptr;_ptr=NULL;}}public:T&operator*()//没有参数{return*_ptr;}T*operator->()//没有参数{return_ptr;}private:T*_ptr;};

评价:

这种实现方式很好的解决了old的版本特殊场景的野指针问题,但是它与原生指针的差别更大,由于实现了完全的权限转移,所以导致在拷贝构造和赋值之后只有一个指针可以使用,而其他指针都置为NULL,使用很不方便,而且还很容易导致对于NULL指针的截引用,导致程序崩溃,其危害也是比较大的。


3、针对以上的问题,在Boost库中引入了简单粗暴的解决方法scoped_ptr,直接不允许其拷贝构造和赋值(ps:新的C++11标准中叫做unique_ptr)

解决办法:将赋值和拷贝构造函数只声明,不实现,切记,将其声明为protected或者private以免在类的外部对其进行破环性处理,同时也是提高了代码的安全性。

template<typenameT>classScopedPtr{public:ScopedPtr(T*ptr):_ptr(ptr){}~ScopedPtr(){if(_ptr!=NULL){delete_ptr;}}protected://将其声明为protected或者private不能声明为publicScopedPtr(ScopedPtr<T>&sp);ScopedPtr<T>operator=(ScopedPtr<T>&sp);public:T&operator*(){return*_ptr;}T*operator->(){return_ptr;}private:T*_ptr;};

评价:

在一般的情况下,如果不需要对于指针的内容进行拷贝,赋值操作,而只是为了防止内存泄漏的发生,该只能指针完全可以满足需求。


4、允许拷贝构造和赋值的shared_ptr模拟实现

解决办法:

通过为每个空间多开辟4个字节做为引用计数器,在拷贝构造、赋值、析构时用计数器来解决。

template<typenameT>classSharedPtr{public:SharedPtr(T*ptr):_ptr(ptr),_pCount(newint(1)){}SharedPtr(constSharedPtr<T>&sp){_ptr=sp._ptr;_pCount=sp._pCount;(*_pCount)++;}SharedPtr<T>operator=(SharedPtr<T>sp){swap(_ptr,sp._ptr);swap(_pCount,sp._pCount);return*this;}~SharedPtr(){_Realse();}public:void_Realse(){if(--(*_pCount)==0){delete_ptr;delete_pCount;}}public:T&operator*(){return*_ptr;}T*operator->(){return_ptr;}private:T*_ptr;int*_pCount;};

评价:

简化版智能指针SharedPtr看起来不错,但是实际上存在以下问题:

1)循环引用

2)定置删除器

循环引用

template<classT>structNode{public:~Node(){cout<<"delete:"<<this<<endl;}public:shared_ptr<Node>_prev;shared_ptr<Node>_next;/*weak_ptr<Node>_prev;weak_ptr<Node>_next;*/};voidtest_round()//循环引用{shared_ptr<Node>cur(newNode());shared_ptr<Node>next(newNode());cout<<"连接前:"<<endl;cout<<"cur:"<<cur.use_count()<<endl;cout<<"next:"<<next.use_count()<<endl;cur->_next=next;next->_prev=cur;cout<<"连接后:"<<endl;cout<<"cur:"<<cur.use_count()<<endl;cout<<"next:"<<next.use_count()<<endl;/*shared_ptr<Node>cur(newNode());weak_ptr<Node>wp1(cur);*/}

解决办法:

使用一个弱引用智能指针(weak_ptr)来打破循环引用(weak_ptr不增加引用计数)

template<classT>structNode{public:~Node(){cout<<"delete:"<<this<<endl;}public://shared_ptr<Node>_prev;//shared_ptr<Node>_next;weak_ptr<Node>_prev;weak_ptr<Node>_next;};voidtest_round()//循环引用{shared_ptr<Node>cur(newNode());shared_ptr<Node>next(newNode());cout<<"连接前:"<<endl;cout<<"cur:"<<cur.use_count()<<endl;cout<<"next:"<<next.use_count()<<endl;cur->_next=next;next->_prev=cur;cout<<"连接后:"<<endl;cout<<"cur:"<<cur.use_count()<<endl;cout<<"next:"<<next.use_count()<<endl;/*shared_ptr<Node>cur(newNode());weak_ptr<Node>wp1(cur);*/}


定置删除器

在shared_ptr中只能处理释放new开辟的空间,而对于malloc,以及fopen打开的文件指针不能处理,为了能够全面的处理各种各样的指针,所以提出了定制删除器,而其实现则是通过仿函数(通过对()运算符的重载)来实现。

1)模拟实现的解决

template<typenameT,typenameD>classSharedPtr{public:SharedPtr(T*ptr):_ptr(ptr),_pCount(newint(1)){}SharedPtr(T*ptr,Ddel):_ptr(ptr),_pCount(newint(1)),_del(del){}SharedPtr(constSharedPtr<T,D>&sp){_ptr=sp._ptr;_pCount=sp._pCount;(*_pCount)++;}SharedPtr<T,D>operator=(SharedPtr<T,D>sp){swap(_ptr,sp._ptr);swap(_pCount,sp._pCount);return*this;}~SharedPtr(){_Realse();}public:void_Realse(){if(--(*_pCount)==0){_del(_ptr);delete_pCount;}}public:T&operator*(){return*_ptr;}T*operator->(){return_ptr;}private:T*_ptr;int*_pCount;D_del;};template<typenameT>structDeafaultDel{voidoperator()(T*ptr){cout<<"DeafaultDel:"<<ptr<<endl;}};template<typenameT>structFree{voidoperator()(T*ptr){cout<<"Free:"<<ptr<<endl;}};template<typenameT>structFclose{voidoperator()(T*ptr){cout<<"Fclose:"<<ptr<<endl;fclose(ptr);}};//测试代码voidTestDelete(){int*p1=(int*)malloc(sizeof(int));SharedPtr<int,Free<int>>sp1(p1);FILE*p2=fopen("test.txt","r");SharedPtr<FILE,Fclose<FILE>>sp2(p2);}

测试结果

2)系统shared_ptr的解决

structFree{voidoperator()(void*ptr){cout<<"Free:"<<ptr<<endl;free(ptr);}};structFclose{voidoperator()(FILE*ptr){cout<<"Fclose"<<ptr<<endl;fclose(ptr);}};//测试代码voidtest_shared_ptr_delete(){int*p1=(int*)malloc(sizeof(int));shared_ptr<int>sp1(p1,Free());FILE*p2=fopen("test.txt","r");shared_ptr<FILE>sp2(p2,Fclose());//崩溃}

测试结果


总结:

1、如果需要使用智能指针的话,scoped_ptr完全可以胜任。在非常特殊的情况下,例如对STL容器对象,应该只使用shared_ptr,任何情况下都不要使用auto_ptr.

2、使用时尽量局部化,因为其是通过调用其析构函数来实现资源的回收。