zoukankan      html  css  js  c++  java
  • auto_ptr源码剖析

      auto_ptr是当前C++标准库(STL)中提供的一种智能指针包含头文件 #include<memory> 便可以使用。auto_ptr 能够方便的管理单个堆内存对象,下面贴出SGI中的auto_ptr源码。

      1 /*
      2  * Copyright (c) 1997-1999
      3  * Silicon Graphics Computer Systems, Inc.
      4  *
      5  * Permission to use, copy, modify, distribute and sell this software
      6  * and its documentation for any purpose is hereby granted without fee,
      7  * provided that the above copyright notice appear in all copies and
      8  * that both that copyright notice and this permission notice appear
      9  * in supporting documentation.  Silicon Graphics makes no
     10  * representations about the suitability of this software for any
     11  * purpose.  It is provided "as is" without express or implied warranty.
     12  *
     13  */
     14  
     15 #ifndef __SGI_STL_MEMORY
     16 #define __SGI_STL_MEMORY
     17  
     18 #include <stl_algobase.h>
     19 #include <stl_alloc.h>
     20 #include <stl_construct.h>
     21 #include <stl_tempbuf.h>
     22 #include <stl_uninitialized.h>
     23 #include <stl_raw_storage_iter.h>
     24  
     25  
     26 __STL_BEGIN_NAMESPACE
     27  
     28 #if defined(__SGI_STL_USE_AUTO_PTR_CONVERSIONS) && 
     29     defined(__STL_MEMBER_TEMPLATES)
     30  
     31 template<class _Tp1> struct auto_ptr_ref {
     32   _Tp1* _M_ptr;
     33   auto_ptr_ref(_Tp1* __p) : _M_ptr(__p) {}
     34 };
     35  
     36 #endif
     37  
     38 template <class _Tp> class auto_ptr {
     39 private:
     40   _Tp* _M_ptr;
     41  
     42 public:
     43   typedef _Tp element_type;
     44  
     45   explicit auto_ptr(_Tp* __p = 0) __STL_NOTHROW : _M_ptr(__p) {}
     46   auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {}
     47  
     48 #ifdef __STL_MEMBER_TEMPLATES
     49   template <class _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) __STL_NOTHROW
     50     : _M_ptr(__a.release()) {}
     51 #endif /* __STL_MEMBER_TEMPLATES */
     52  
     53   auto_ptr& operator=(auto_ptr& __a) __STL_NOTHROW {
     54     if (&__a != this) {
     55       delete _M_ptr;
     56       _M_ptr = __a.release();
     57     }
     58     return *this;
     59   }
     60  
     61 #ifdef __STL_MEMBER_TEMPLATES
     62   template <class _Tp1>
     63   auto_ptr& operator=(auto_ptr<_Tp1>& __a) __STL_NOTHROW {
     64     if (__a.get() != this->get()) {
     65       delete _M_ptr;
     66       _M_ptr = __a.release();
     67     }
     68     return *this;
     69   }
     70 #endif /* __STL_MEMBER_TEMPLATES */
     71  
     72   // Note: The C++ standard says there is supposed to be an empty throw
     73   // specification here, but omitting it is standard conforming.  Its 
     74   // presence can be detected only if _Tp::~_Tp() throws, but (17.4.3.6/2)
     75   // this is prohibited.
     76   ~auto_ptr() { delete _M_ptr; }
     77  
     78   _Tp& operator*() const __STL_NOTHROW {
     79     return *_M_ptr;
     80   }
     81   _Tp* operator->() const __STL_NOTHROW {
     82     return _M_ptr;
     83   }
     84   _Tp* get() const __STL_NOTHROW {
     85     return _M_ptr;
     86   }
     87   _Tp* release() __STL_NOTHROW {
     88     _Tp* __tmp = _M_ptr;
     89     _M_ptr = 0;
     90     return __tmp;
     91   }
     92   void reset(_Tp* __p = 0) __STL_NOTHROW {
     93     if (__p != _M_ptr) {
     94       delete _M_ptr;
     95       _M_ptr = __p;
     96     }
     97   }
     98  
     99   // According to the C++ standard, these conversions are required.  Most
    100   // present-day compilers, however, do not enforce that requirement---and, 
    101   // in fact, most present-day compilers do not support the language 
    102   // features that these conversions rely on.
    103  
    104 #if defined(__SGI_STL_USE_AUTO_PTR_CONVERSIONS) && 
    105     defined(__STL_MEMBER_TEMPLATES)
    106  
    107 public:
    108   auto_ptr(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW
    109     : _M_ptr(__ref._M_ptr) {}
    110  
    111   auto_ptr& operator=(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW {
    112     if (__ref._M_ptr != this->get()) {
    113       delete _M_ptr;
    114       _M_ptr = __ref._M_ptr;
    115     }
    116     return *this;
    117   }
    118  
    119   template <class _Tp1> operator auto_ptr_ref<_Tp1>() __STL_NOTHROW 
    120     { return auto_ptr_ref<_Tp1>(this->release()); }
    121   template <class _Tp1> operator auto_ptr<_Tp1>() __STL_NOTHROW
    122     { return auto_ptr<_Tp1>(this->release()); }
    123  
    124 #endif /* auto ptr conversions && member templates */
    125 };
    126  
    127 __STL_END_NAMESPACE
    128  
    129 #endif /* __SGI_STL_MEMORY */
    View Code

      相信对STL有了解的同学对auto_ptr不会陌生,使用起来很方便,目的是为了方便管理堆上创建的指针,防止内存泄露。这里只谈谈源码中一些值得注意的要点:

    • auto_ptr构造函数中使用explicit,防止复制/拷贝时不必要的类型转换,而在auto_ptr定义对象时必须显示调用初始化式,不能使用赋值操作符进行隐式转换。
    • auto_ptr析构函数将原生指针delete掉,这里可能出现重复释放的问题:
      1 int main()
      2 {
      3     int* p = new int(12);
      4     auto_ptr<int> aptr1(p);
      5     auto_ptr<int> aptr2(p);
      6 
      7     return 0;
      8 }
      View Code

      上述代码中由于aptr1和aptr2均指向同一块内存p,两个对象析构时会出现同一块内存重复释放的问题,所以应避免上述编码方式。

    • auto_ptr析构函数调用操作符delete,而不是delete[],所以auto_ptr不能用于管理分配在堆上的数组。
    • auto_ptr拷贝构造函数、赋值操作符参数声明为 auto_ptr& __a,而一般类中定义的拷贝构造函数、赋值操作符,参数类型为const T&。拷贝函数、赋值操作符中又调用了release()函数,注意release()函数会对auto_ptr中的原生指针重新赋值,在拷贝、赋值时,需要特别注意。
      1 int main()
      2 {
      3     int* p = new int(12);
      4     auto_ptr<int> aptr1(p);
      5     auto_ptr<int> aptr2(aptr1);
      6     cout<<*aptr1<<endl;
      7 
      8     return 0;
      9 }
      View Code

      上述代码中line6,输出*aptr1时,会出现undefine行为,因为在构造aptr2时,aptr1中原生指针已经被赋值为NULL。这种情况会更隐蔽的出现在函数调用传参或者返回时:

       1 void foo(auto_ptr<int> ap)
       2 {
       3     cout<<*ap<<endl;
       4     return;
       5 }
       6 
       7 int main()
       8 {
       9     auto_ptr<int> scope(new int(12));
      10     foo(scope);
      11     cout<<*scope<<endl;
      12 
      13     return 0;
      14 }
      View Code
      上述代码line11,输出*scope时,同样会出现undefine行为,因为在调用foo时,scope原生指针已经被赋值为NULL。auto_ptr的这种特点(缺点),导致容器不能很好的支持auto_ptr类。容器内元素的类型约束为元素类型必须支持赋值运算(引用不支持赋值,所以引用类型不能被放入容器),元素类型的对象必须可以复制(IO库类型不支持复制或赋值,所以IO类型对象不能放入容器),对于auto_ptr这种类型来说,它的赋值和复制已不是传统意义上的概念,所以放入容器中会出现很多未定义的后果。同样在作为函数入参时,除非用const & 进行修饰,否则也会出现未定义操作。
    • auto_ptr拷贝构造函数、赋值操作符使用了成员函数模板,用途在于使auto_ptr实现继承体系的转换,查看如下代码:
       1 class Person 
       2 {
       3 public:
       4     virtual void action()const = 0;
       5 };
       6 class Student : public Person 
       7 {
       8 public:
       9     virtual void action()const
      10     {
      11         cout<<"Student action"<<endl;
      12     }
      13 };
      14 class Teacher : public Person
      15 {
      16 public:
      17     virtual void action()const
      18     {
      19         cout<<"Teacher action"<<endl;
      20     }
      21 };
      22 
      23 void raw_action(const Person *p)
      24 {
      25     p->action();
      26 }
      27 
      28 void apt_action(auto_ptr<Person> apt)
      29 {
      30     apt->action();
      31 }
      32 
      33 int main()
      34 {
      35     Student * pStu = new Student;
      36     Teacher * pTea = new Teacher;
      37     raw_action(pStu);
      38     raw_action(pTea);
      39 
      40     auto_ptr<Student> apStu(pStu);
      41     auto_ptr<Teacher> apTea(pTea);
      42     
      43     //这里如果直接调用会出现二义性,有多个从auto_ptr<Derived>到auto_ptr<Base>的转换:构造函数和operator转换操作符
      44     //apt_action(apStu);
      45     //apt_action(apTea)
      46     auto_ptr<Person> ap1(apStu);
      47     auto_ptr<Person> ap2(apTea);
      48 
      49         apt_action(ap1);
      50         apt_action(ap2);
      51 
      52     return 0;
      53 }
      View Code

      代码中使用原生指针可以轻松实现多态,而对于auto_ptr,auto_ptr<Person>与auto_ptr<Student>和auto_ptr<Teacher>本不构成继承体系,由于有了成员模板函数才使得它们能够完成有派生类向基类的转换。

    • auto_ptr中定义了一个struct auto_ptr_ref,它使我们可以拷贝和赋值临时的auto_ptr:
       1 auto_ptr<int> fun()
       2 {
       3     return auto_ptr<int>(new int(12));
       4 }
       5 
       6 int main()
       7 {
       8     auto_ptr<int> ap1 = auto_ptr<int>(new int(12));//用一个临时变量进行赋值初始化
       9 
      10     auto_ptr<int> ap2 (fun());//调用fun()返回一个临时auto_ptr进行初始化
      11     
      12     return 0;
      13 }
      View Code

      由于auto_ptr的控制复制函数入参类型都是非const的,而临时对象只能绑定在const &或者具体类型上,而对于第一句会调用auto_ptr的拷贝构造函数,但是拷贝构造函数入参是一个非const引用,无法将临时对象绑定在非const引用上,所以编译不会通过;对于fun()函数返回的auto_ptr对象,同样会调用拷贝构造,而此时的临时对象同样无法绑定在非const引用上。auto_ptr_ref的引入就是为了解决这个问题。具体来说,auto_ptr<int> ap1 = auto_ptr<int>(new int(12)); 首先创建了一个原生指针指向12的auto_ptr的临时对象,根据

      1 template <class _Tp1> operator auto_ptr_ref<_Tp1>() __STL_NOTHROW
      2     { return auto_ptr_ref<_Tp1>(this->release()); }
      View Code
      进行转换,返回一个原生指针指向12的 auto_ptr_ref<int>的临时对象(右值),再根据
      1 auto_ptr(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW
      2     : _M_ptr(__ref._M_ptr) {}
      View Code

      此构造函数的入参时值类型 auto_ptr_ref<_Tp> __ref 不是引用,而是值传递,所以临时对象(右值)可以进行绑定,auto_ptr_ref<_Tp>先调用默认生成的拷贝构造函数,生成入参 __ref,在 _M_ptr(__ref._M_ptr)之后临时对象析构。

      

      auto_ptr是C++标准中功能最简单的智能指针,主要为了解决资源泄漏的问题。它的实现原理其实是RAII,在构造的时候获取资源,在析构的时候释放资源,从源码中看出实现有很多巧妙之处。上面提到了一些auto_ptr的不足之处,可以使用更高级的带有引用计数的智能指针来代替。

  • 相关阅读:
    LINUX的LAMP环境搭配
    Jquery添加元素append及阻止表单提交submit
    django上传文件
    Html5本地存储LocalStorage
    Html5离线缓存详细讲解
    html5的canvas绘制迷宫地图
    EXT.NET复杂布局(二)——报表
    Silverlight——施工计划日报表(一)
    Silverlight——施工计划日报表(二)
    前端学HTTP之网络基础
  • 原文地址:https://www.cnblogs.com/Tour/p/4036493.html
Copyright © 2011-2022 走看看