智能指针auto_ptr & shared_ptr

转载:智能指针auto_ptr

很多人听说过标准auto_ptr智能指针机制,但并不是每个人都天天使用它。这真是个遗憾,因为auto_ptr优雅地解决了C++设计和编码中常见的问题,正确地使用它可以生成健壮的代码。本文阐述了如何正确运用auto_ptr来让你的代码更加安全——以及如何避免对auto_ptr危险但常见的误用,这些误用会引发间断性发作、难以诊断的bug。

1.为什么称它为“自动”指针?
auto_ptr只是众多可能的智能指针之一。许多商业库提供了更复杂的智能指针,用途广泛而令人惊异,从管理引用的数量到提供先进的代理服务。可以把标准C++ auto_ptr看作智能指针的Ford Escort(elmar注:可能指福特的一种适合家居的车型):一个简易、通用的智能指针,它不包含所有的小技巧,不像专用的或高性能的智能指针那么奢华,但是它可以很好的完成许多普遍的工作,它很适合日常性的使用。

auto_ptr所做的事情,就是动态分配对象以及当对象不再需要时自动执行清理。这里是一个简单的代码示例,没有使用auto_ptr所以不安全:
    // 示例 1(a): 原始代码
    //

#include<memory>

using namespace std;
    void f()
    {
      T* pt( new T );

      /*...更多的代码...*/

      delete pt;
    }

我们大多数人每天写类似的代码。如果f()函数只有三行并且不会有任何意外,这么做可能挺好的。但是如果f()从不执行delete语句,或者是由于过早的返回,或者是由于执行函数体时抛出了异常,那么这个被分配的对象就没有被删除,从而我们产生了一个经典的内存泄漏。

能让示例1(a)安全的简单办法是把指针封装在一个“智能的”类似于指针的对象里,这个对象拥有这个指针并且能在析构时自动删除这个指针所指的对象。因为这个智能指针可以简单的当成一个自动的对象(这就是说,它出了作用域时会自动毁灭),所以很自然的把它称之为“智能”指针:

    // 示例 1(b): 安全代码, 使用了auto_ptr
    //
    void f()
    {
      auto_ptr<T> pt( new T );

      /*...更多的代码...*/

    } // 酷: 当pt出了作用域时析构函数被调用,
      // 从而对象被自动删除

现在代码不会泄漏T类型的对象,不管这个函数是正常退出还是抛出了异常,因为pt的析构函数总是会在出栈时被调用。清理会自动进行。

最后,使用一个auto_ptr就像使用一个内建的指针一样容易,而且如果想要“撤销”资源,重新采用手动的所有权,我们只要调用release():

    // 示例 2: 使用一个 auto_ptr
    //
    void g()
    {
      T* pt1 = new T;
      // 现在,我们有了一个分配好的对象

      // 将所有权传给了一个auto_ptr对象
      auto_ptr<T> pt2( pt1 );

      // 使用auto_ptr就像我们以前使用简单指针一样
      *pt2 = 12;       // 就像 "*pt1 = 12;"
      pt2->SomeFunc(); // 就像 "pt1->SomeFunc();"

      // 用get()来获得指针的值
      assert( pt1 == pt2.get() );

      // 用release()来撤销所有权
      T* pt3 = pt2.release();

      // 自己删除这个对象,因为现在
      // 没有任何auto_ptr拥有这个对象
      delete pt3;

    } // pt2不再拥有任何指针,所以不要
      // 试图删除它...ok,不要重复删除

最后,我们可以使用auto_ptr的reset()函数来重置auto_ptr使之拥有另一个对象。如果这个auto_ptr已经拥有了一个对象,那么,它会先删除已经拥有的对象,因此调用reset()就如同销毁这个auto_ptr,然后新建一个并拥有一个新对象:

    // 示例 3: 使用reset()
    //
    void h()
    {
      auto_ptr<T> pt( new T(1) );

      pt.reset( new T(2) );
        // 删除由"new T(1)"分配出来的第一个T

    } // 最后,pt出了作用域,
      // 第二个T也被删除了

 

转载地址:c++ shared_ptr智能指针使用注意事项

shared_ptr在boost中地位相当重要,其行为最接近原始指针,但又比指针更加安全,甚至还能提供基本的线程安全保证。它基本上解决了在使用c++开发过程中不可避免的使用指针而遇到的许多问题,常见的毫无疑问是内存泄漏和内存的提前释放,还有一些关于指针内存申请而产生的异常问题等。而要想较好的使用shared_ptr来完全取代指针绝非易事。下面简要说说使用shared_ptr需要注意的问题。

       1.share_ptr是一个类,它产生的是一个类对象,而不是一个原生的指针对象,但是为了减少类对象与针对对象使用的差异性,所以share_ptr类故意重载了两种常见的指针操作符: *和->。从而share_ptr与普通指针使用方式一样。简言之,就是share_ptr生成的一个包含类型指针容器对象,它封装了指针对象,对指针对象负全责,包括生成、释放等

      2.特别需要注意的是,share_ptr的转型函数不能使用c++常用的转型函数,即static_cast,dynamic_cast,const_cast,而要使static_pointer_cast,dynamic_pointer_cast,const_pointer_cast。原因有两个:static_cast,dynamic_cast,const_cast的功能是转换成对应的模版类型,即static_cast<T*>其实是转换成类型为T的指针;前面说了share_ptr生成的一个包含类型指针容器对象,使用简单的c++转型函数是将share_ptr对象转型为模版指针对象,这完全违背了使用share_ptr的初衷(除非你确确实实有这种需要!),导致转型的模版指针对象不能采用share_ptr进行管理。因为上面的两个原因:share_ptr为了支持转型,所以提供了类似的转型函数即static_pointer_cast<T>,从而使转型后仍然为shared_pointer对象,仍然对指针进行管理;

     3.share_ptr类重载了许多的构造函数,其中包含无参的构造函数用来创建一个持有空指针的share_ptr对象,,注意它不等价于持有void*型的指针对象。要想生成一个存储void*型的指针,可以使用share_ptr(void*)构造函数来构造,它相当于一个泛型的指针容器,拥有容纳任意类型的能力,但是将包含void*指针的shared_ptr对象使用注意2中的转型函数转型为某种类型的指针会使代码不够安全,一般不要使用;

     4.share_ptr完美支持标准容器,并且不需要担心资源泄漏。而标准容易在使用指针对象时需要特别的小心,对指针需要额外的管理。下面用代码范例来演示如何将share_ptr应用与标准容器:

[cpp] view plaincopy
 
  1. <span style="font-size:14px;"><strong>#include <boost/make_shared.hpp>  
  2. #include <vector>  
  3. using namespace std;  
  4. using namespace boost;  
  5. int main ()  
  6. {  
  7.     typedef vector< shared_ptr<int> > sharedContainers;  
  8.     sharedContainers sharedArray(10);  
  9.     for(sharedContainers::interator pos = sharedArray.begin() , int i=0 ;
  10.        pos!=sharedArray.end();++pos)   
  11.      {  
  12.             *pos = make_shared<int>(++i);  
  13.             cout<<*(*pos)<<" ";  
  14.      }  
  15.       cout<<endl;  
  16. }</strong></span>  

    注意上面的代码中的头文件#include <boost/make_shared.hpp>,不是常见的#include <boost/smart_ptr.hpp>,这是因为make_shared.hpp中提供了一个自由工厂函数make_shared<T>(),来消除显示的new调用,其实是模仿了标准库中产生pair的函数make_pair().因为make_shared.hpp中包含了smart_ptr.hpp,所以加入了make_shared.hpp就不需要再声明smart_ptr.hpp头文件了。而smart_ptr.hpp没有包含make_shared.hpp,所以必须要单独写出make_shared.hpp

     5.share_ptr使用赋值操作符可以从另外一个share_ptr或auto_ptr获得指针的管理权,其行为等同于构造函数,但它完全不能等同于一种拷贝,只是将原来的对象的引用技术加1,说白了就是共享资源;而实际的指针赋值确是一种浅拷贝操作,关于深拷贝与浅拷贝可参与该篇文章:c++ 深拷贝与浅拷贝下面用代码范例来share_ptr的赋值和复制操作:(继续上面的例子)

[cpp] view plaincopy
 
  1. <span style="font-size:14px;"><strong>#include <boost/make_shared.hpp>  
  2. #include <vector>  
  3. using namespace std;  
  4. using namespace boost;  
  5.   
  6. int main (int argc, const char * argv[])  
  7. {  
  8.   
  9.     typedef vector< shared_ptr<int> > sharedContainers;  
  10.     sharedContainers sharedArray(10);  
  11.     int i=0;  
  12.     for(sharedContainers::iterator pos = sharedArray.begin() ;
  13.             pos!=sharedArray.end();++pos)   
  14.     {  
  15.         *pos = make_shared<int>(++i);  
  16.     }  
  17.     cout<<"sharedArray[5]的初始值:"<<*sharedArray[5]<<endl;  
  18.     cout<<"sharedArray[5]的初始引用计数为:"<<sharedArray[5].use_count()<<endl;  
  19.     shared_ptr<int> p1 = sharedArray[5];  
  20.     *p1 = 10;  
  21.     cout<<"sharedArray[5]经过赋值后的值:"<<*sharedArray[5]<<endl;  
  22.     cout<<"sharedArray[5]赋值后的引用计数为:"<<sharedArray[5].use_count()<<endl;  
  23.     shared_ptr<int> p2(sharedArray[5]);  
  24.     cout<<"sharedArray[5]复制和赋值后的引用计数为:"<<sharedArray[5].use_count()<<endl;  
  25.     cout<<"sharedArray[5]的地址:
  26. "<<sharedArray[5]<<",p1的地址:"<<p1<<",p2的地址:"<<p2<<endl;  
  27.     *p2 = 5;  
  28.     cout<<"sharedArray[5]经过复制后的值:"<<*sharedArray[5]<<endl;  
  29.     return 0;  
  30.       
  31. }</strong></span>  

其输出结果为:

sharedArray[5]的初始值:6
sharedArray[5]的初始引用计数为:1
sharedArray[5]经过赋值后的值:10
sharedArray[5]赋值后的引用计数为:2
sharedArray[5]复制和赋值后的引用计数为:3
sharedArray[5]的地址:0x10010092c,p1的地址:0x10010092c,p2的地址:0x10010092c
sharedArray[5]经过复制后的值:5

可以看到,经过赋值和复制后的sharedArray[5],其引用计数增1。而且经过赋值和复制后的sharedArray[5]对象p1和p2所指向的地址完全相同。所以随便改变其中之一的值,另外两个对象指向的值也随之改变,这也是共享资源的精髓吧!

      6. share_ptr针对封装的指针要求,可以定制删除器,即使用构造函数shared_ptr(Y *p , D d)来构造shared_ptr指针。其中第一个参数是要被管理的指针,其含义与其他构造函数一样。而第二个参数,即删除器参数d则告诉shared_ptr在析构时不是使用普通的delete方法来析构指针p,而是用方法d来操作,即用d(p)来代替delete p。这里的d只需要能够像函数一样被调用,所以d既可以是一个函数对象,也可以是一个函数指针。这种删除器d比较适用于指针对象p在删除时有一定的要求,例如某中类指针,有专门的析构函数,但切记d的实参对象行为必须与delete一样,不能抛出异常,并且是可拷贝的。

      上面说了这么多,其实这只是shared_ptr的一小部分,还有很多涉及c++哲学方面的问题需要注意,例如延时释放、包装成员函数等。

      本文如有错误,欢迎指正,对此十分感谢!

原文地址:https://www.cnblogs.com/xxiaoye/p/3674869.html