剖析智能指针之auto_ptr

STL中的智能指针(Smart Pointer)及其源码剖析:  std :: auto_ptr

  • auto_ptr 是STL中的智能指针家族的成员之一,它管理由  new expression 获得的对象,在  auto_ptr 对象销毁时,他所管理的对象也会自动被  delete 掉。
  • auto_ptr 的拷贝构造函数和拷贝的赋值会改变右边的值,并且拷贝的副本不会等于原始的,被拷贝的那个  auto_ptr 对象的值(实际上,auto_ptr 的拷贝构造函数和拷贝赋值函数会让左边的值接管右hand value所管理的对象。)
  • 由于不一样的拷贝语义,  auto_ptr 不适用于标准容器,因此,更建议使用std::unique_ptr

一. auto_ptr 的使用

1. auto_ptr 的声明

  //(until C++17) 
  //(deprecated since C++11)
  template<class T> class auto_ptr;
  template<> class auto_ptr<void>;
   
   
  • 1
  • 2
  • 3
  • 4

2. auto_ptr 的构造函数

  //deprecated...

  explicit auto_ptr(X* p = 0);     (1)
  auto_ptr(auto_ptr& r);           (2)

  template<class Y>                (3)
  auto_ptr<auto_ptr<Y>& r);

  template<class Y>                (4)
  auto_ptr(auto_ptr_ref<Y> m);   
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

(1) 构造 auto_ptr 对象, 让它管理 p 指向的对象。 
(2) 构造 auto_ptr 对象,让它接管 r 管理的对象。实际上新的 auto_ptr 对象是靠 r.release() 函数获得管理权的。因此,r 失去了管理权。 
(3) 这个构造函数和 (2) 类似, 主要针对能隐式转换为 T* 类型的 Y*。 
(4) 构造 auto_ptr 对象, 让它接管 auto_ptr_ref<Y> 类型的 m 管理的对象。而m 是通过 p.release() 从 auto_ptr 对象 p 中获取管理权的。

Q: what is auto_ptr_ref, what it achieves and how it achieves it ? 

A: It is rather confusing. Basically, auto_ptr_ref exists because the auto_ptr copy constructor isn’t really a copy constructor in the standard sense of the word.

Copy constructors typically have a signature that looks like this:

X(const X &b); 
The auto_ptr copy constructor has a signature that looks like this:

X(X &b) 
This is because auto_ptr needs to modify the object being copied from in order to set its pointer to 0 to facilitate the ownership semantics of auto_ptr.

Sometimes, temporaries cannot match a copy constructor that doesn’t declare its argument const. This is where auto_ptr_ref comes in. The compiler won’t be able to call the non-const version of the copy constructor, but it can call the conversion operator. The conversion operator creates an auto_ptr_ref object that’s just sort of a temporary holder for the pointer. The auto_ptr constructor or operator = is called with the auto_ptr_ref argument.

如果您注意到,auto_ptr中的自动转换为auto_ptr_ref的转换运算符在源auto_ptr上执行释放,就像复制构造函数一样。

这是幕后发生的一种奇怪的小舞,因为auto_ptr修改了被复制的东西。

简单地总结:  auto_ptr_ref 主要解决用右值来构造  auto_ptr 的情况。因为,  auto_ptr(auto_ptr& r) 构造函数只能以左值引用做参数。当右值来构造  auto_ptr_ref 的时候,实际上实现过程如下(这其实是移动语义的早期实现版本): 
SmartPtr_auto_ptr

3.  auto_ptr的构函数:销毁管理的对象。

 ~auto_ptr(); // deprecated
   
   
  • 1

4.  拷贝赋值函数

    //deprecated

    auto_ptr& operator=(auto_ptr& r);     (1)

    template<class Y>                     (2)
    auto_ptr& operator=(auto_ptr<Y>& r);

    auto_ptr& operator=(auto_ptr_ref m);  (3)
   
   
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8

auto_ptr 的拷贝赋值函数会让左边的值接管右边的值所管理的对象。

5.  隐式类型转换函数

    //deprecated...

    template<class Y>                     (1)
    operator auto_ptr_ref<Y>();

    template<class Y>                     (2)
    operator auto_ptr<Y>();
   
   
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7

(1)将该对象隐式转换为  auto_ptr_ref<Y> 类型。 
(2)将该对象隐式转换为  auto_ptr<Y> 类型。

6.其他函数(auto_ptr :: get,  auto_ptr :: operator *,auto_ptr :: operator->,  auto_ptr :: reset,  auto_ptr :: release

    //deprecated...

    T* get() const;                       (1)

    T& operator*() const;                 (2)
    T* operator->() const;                (3)

    void reset(T* p = 0);                 (4)

    T* release();                         (5)
   
   
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10

(1)该报道查看  *this 所管理对象的指针。 
(2)该报道查看  *this 所管理对象。 
(3)该报道查看  *this 所管理对象的指针。 
(4)让  *this 管理  p 所指向的对象,如果  *this 已有管理的对象,则先  delete 掉当前管理的对象。 
(5)移交出  *this 所管理对象的管理权。返回  *this 所管理对象的指针,并将  *this 内部的指针置为空。

7.例子

  • 代码

    
    #include <iostream>
    
    
    #include <string>
    
    
    #include <memory>
    
    
    using namespace std;
    
    // 展示测试结果 
    template<class Ty>
    void Test(auto_ptr<Ty>& showPtr, string name, string hint)
    {
        cout << hint;
    
        if(showPtr.get() == nullptr) cout << name << ".get() == nullptr" << endl;
        else cout << "*" << name << ".get() == " << *showPtr.get() << endl; 
    }
    
    // for test...
    class Base
    {
    public:
        Base(double pi = 0.0) : m_pi(pi){
            //...
        }
        virtual void ShowName() const
        {
            cout << "Base Object";
        }
        double m_pi;
    };
    class Derive : public Base
    {
    public: 
    virtual void ShowName() const
        {
            cout << "Derive Object";
        }
    };
    
    ostream& operator<<(ostream& os, const Base& b)
    {
        b.ShowName();
        return os;
    }
    ostream& operator<<(ostream& os, const Derive& b)
    {
        b.ShowName();
        return os;
    }
    
    int main()
    {
        // 构造函数... 
        // explicit auto_ptr(X* p = 0);     (1)
        auto_ptr<int> intPtr1;
        Test(intPtr1, "intPtr1", "explicit auto_ptr(X* p = 0)...\n");
        int* ptr = new int(2);
        auto_ptr<int> intPtr2(ptr);
        Test(intPtr2, "intPtr2", "");
        Derive* dp = new Derive;
        auto_ptr<Derive> dptr(dp);
        Test(dptr, "dptr", "");
        cout << endl;
        // auto_ptr(auto_ptr& r);           (2)
        auto_ptr<int> intPtr3(intPtr2);
        Test(intPtr3, "intPtr3", "auto_ptr(auto_ptr& r)...\n"); 
        Test(intPtr2, "intPtr2", "intPtr2 失去了对 ptr 的控制权: ");
        cout << endl;
        // template<class Y>                (3)
        // auto_ptr<auto_ptr<Y>& r);
        auto_ptr<Base> bPtr(dptr); 
        Test(bPtr, "bPtr", "template<class Y> auto_ptr<auto_ptr<Y>& r)...\n"); 
        Test(dptr, "dptr", "dptr 失去了对 dp 的控制权: ");
        cout << endl;
        // template<class Y>                (4)
        // auto_ptr(auto_ptr_ref<Y> m);
        auto_ptr_ref<string> ptrRef(new string("many strings"));
        auto_ptr<string> strPtr(ptrRef);  
        Test(strPtr, "strPtr", "template<class Y> auto_ptr(auto_ptr_ref<Y> m)...\n"); 
        cout << endl;
    
        // 拷贝赋值函数
        // auto_ptr& operator=(auto_ptr& r);     (1)
        auto_ptr<int> intPtr4;
        intPtr4 = intPtr3;
        Test(intPtr4, "intPtr4", "auto_ptr& operator=(auto_ptr& r)...\n"); 
        Test(intPtr3, "intPtr3", "intPtr3 失去了对 ptr 的控制权: ");
        cout << endl;
        // template<class Y>                     (2)
        // auto_ptr& operator=(auto_ptr<Y>& r);
        auto_ptr<Derive> derivePtr(new Derive);
        Test(derivePtr, "derivePtr", "template<class Y> auto_ptr& operator=(auto_ptr<Y>& r)...\n");
        auto_ptr<Base> basePtr;
        basePtr = derivePtr;
        Test(basePtr, "basePtr", "basePtr 获得了控制权......\n");
        Test(derivePtr, "derivePtr", "derivePtr 失去了控制权...\n");
        cout << endl;
        // auto_ptr& operator=(auto_ptr_ref m);  (3)
        auto_ptr_ref<string> strPtrRef(new string("auto_ptr_ref strings"));
        auto_ptr<string> strAutoPtr;
        strAutoPtr = strPtrRef;
        Test(strAutoPtr, "strAutoPtr", "auto_ptr& operator=(auto_ptr_ref m)...\n");
        cout << endl;
    
        // 其他函数
        // T* get() const;                       (1)
        int* pAddr = new int(5);
        cout << "pAddr = " << pAddr << endl;
        auto_ptr<int> addr(pAddr);
        cout << "addr.get() = " << addr.get() << endl; 
        cout << endl;
        // T& operator*() const;                 (2)
        cout << "*pAddr = " << *pAddr << endl;
        cout << "*addr.get() = " << *addr.get() << endl; 
        cout << endl;
        // T* operator->() const;                (3)
        Base* pBase = new Base(3.14159);
        auto_ptr<Base> spBase(pBase);
        cout << "pBase->m_pi = " << pBase->m_pi << endl;
        cout << "spBase->m_pi = " << spBase->m_pi << endl;
        cout << endl;
        // void reset(T* p = 0);                 (4)
        intPtr4.reset(new int(-1));
        Test(intPtr4, "intPtr4", "void reset(T* p = 0)...\n");
        // T* release();                         (5) 
        intPtr4.release();
        Test(intPtr4, "intPtr4", "T* release()...\n");
        cout << endl;
    
        return 0;
    }
         
         
    • 1
    • 2
    • 3
    • 4
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 三十
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
  • 运行结果:

    SmartPtr_auto_ptr_test

二。auto_ptr源码剖析(源码出自VS2015)

1.辅助类  auto_ptr_ref 的源码

    template<class _Ty>
    struct auto_ptr_ref
        {   // proxy reference for auto_ptr copying
    explicit auto_ptr_ref(_Ty *_Right)
        : _Ref(_Right)
        {   // construct from generic pointer to auto_ptr ptr
        }

    _Ty *_Ref;  // generic pointer to auto_ptr ptr
    };
   
   
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10

这个辅助类的源码比较简单,没有什么说的。前面也分析过了,这个辅助类其实是为了帮助  auto_ptr 完成右值引用传参而设计的。

2.  auto_ptr 构造函数的源码

    typedef auto_ptr<_Ty> _Myt; // 管理类的类型
    typedef _Ty element_type;   // 被管理元素的类型

    explicit auto_ptr(_Ty *_Ptr = 0)                     (1)
        : _Myptr(_Ptr)
    {   // construct from object pointer
    }

    auto_ptr(_Myt& _Right)                               (2)
        : _Myptr(_Right.release())
    {   // construct by assuming pointer from _Right auto_ptr
    }

    template<class _Other>                               (3)
    auto_ptr(auto_ptr<_Other>& _Right) 
        : _Myptr(_Right.release())
    {   // construct by assuming pointer from _Right
    }

    auto_ptr(auto_ptr_ref<_Tp> Right)                    (4)
        : _Myptr(Right._Ref) {}
   
   
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

其中,  auto_ptr 成员变量  _Ty *_Myptr 指向被它管理的对象。 
(1)从原始指针中获取控制权。注意,由源码可知,  auto_ptr 并没有将原始指针的控制权剥夺(从实现来看,也不能剥夺,因为  Ptr 不是指针引用,无法更改原始指针的指向),原始指针仍然保有对其资源的控制权但是,该资源的释放权实际上已经交给了。  auto_ptr 对象如:

  int* ptr = new int(3);
  auto_ptr<int> autoPtr(ptr);

  //error: 在 autoPtr 生命期结束后会释放ptr指向的资源。
  //如果在这里释放资源, 在 autoPtr 生命期结束后就会崩溃。
  delete ptr; 
   
   
  • 1
  • 2
  • 3
  • 4
  • 6

(2)从  auto_ptr 对象中夺取对资源的控制权。由源码可知,  _Right 将不再保有其资源的控制。注意,这里是左值引用参数,不能接收右值参数。 
(3)与(2)类似。是针对可转换为  _Ty* 类型的  _Other* 类型的构造函数。 
(4)这个构造函数的参数是  auto_ptr_ref<_Ty> 类型的。注意,它不是左值引用类型的参数,因此可以接收右值类型。这也是右值传参的必经之路。

3.  auto_ptr 析构函数的源码

  ~auto_ptr()
  { // destroy the object
    delete _Myptr;
  }
   
   
  • 1
  • 2
  • 3
  • 4

4.  auto_ptr 拷贝赋值函数的源码

  _Myt& operator=(_Myt& _Right)                       (1) 
  { // assign compatible _Right (assume pointer)
    reset(_Right.release());
    return (*this);
  }

  template<class _Other>                              (2)
  _Myt& operator=(auto_ptr<_Other>& _Right)
  { // assign compatible _Right (assume pointer)
    reset(_Right.release());
    return (*this);
  }

  _Myt& operator=(auto_ptr_ref<_Ty> _Right)           (3)
  { // assign compatible _Right._Ref (assume pointer)
    _Ty *_Ptr = _Right._Ref;
    _Right._Ref = 0;    // release old
    reset(_Ptr);    // set new
    return (*this);
  }
   
   
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

这里涉及到解决“自我赋值”(赋值自我)的问题,详情请参阅“有效的C ++”项目11:手柄分配到自我在运算符= 
(1)将同类型的  _Right 管理的资源移交给  *this。其中,reset(_Right.release()) 先将  _Right 的资源以报道查看值的形式移交,然后设置给  *this,这样就防止了“自我赋值”的时候出现问题。(如果  *this 就是  _Right,那么执行完  _Right.release() 后,  *this 管理的资源已经以返回值的形式移交出来作为参数,然后汉语中类似的  reset 给了自己)注意,这里是左值引用参数,不能接收右值参数。 
(2)与(1)类似。的英文针对柯林斯转换为  _Ty* 类型的  _Other* 类型的拷贝产品函数。 
(3)将一个  auto_ptr_ref 类型的变量赋值给  *this,实际上是将资源的控制权移交给  *this。这同样是为了传右值参数而设计的。这个函数体内冗余的代码同样是为了防止  _Right._Ref 等于  _Myptr 的情况。

5.  auto_ptr 隐式类型转换函数

  template<class _Other>                            (1)
  operator auto_ptr_ref<_Other>()
  { // convert to compatible auto_ptr_ref
    _Other *_Cvtptr = _Myptr;   // test implicit conversion
    auto_ptr_ref<_Other> _Ans(_Cvtptr);
    _Myptr = 0; // pass ownership to auto_ptr_ref
    return (_Ans);
  }

  template<class _Other>                            (2)
  operator auto_ptr<_Other>()
  { // convert to compatible auto_ptr
    return (auto_ptr<_Other>(*this));
  }
   
   
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

(1)  auto_ptr 到  auto_ptr_ref 的隐式转换函数。由源码可知,该隐式转换也会剥夺  *this 对资源的管理权。这个转换虽然代码短小,但是能量巨大,右值类型的  auto_ptr 作参数传递时,全靠这个转换函数来起到周转的作用。当然,现在新的C ++标准有更好的方法来解决这个问题 - 右值引用。 
(2)可转换的类型...

6.  auto_ptr 其他函数

  _Ty *get() const                        (1)
  {// return wrapped pointer
    return (_Myptr);
  }

  _Ty& operator*() const                  (2)
  { // return designated value
    if (_Myptr == 0)
        _DEBUG_ERROR("auto_ptr not dereferencable"); 
    return (*get());
  }

  _Ty *operator->() const                 (3)
  { // return pointer to class object
    if (_Myptr == 0)
        _DEBUG_ERROR("auto_ptr not dereferencable");
    return (get());
  }

  void reset(_Ty *_Ptr = 0)               (4)
  { // destroy designated object and store new pointer
    if (_Ptr != _Myptr)
      delete _Myptr;
      _Myptr = _Ptr;
  }

  _Ty *release()                          (5)
  { // return wrapped pointer and give up ownership
    _Ty *_Tmp = _Myptr;
    _Myptr = 0;
    return (_Tmp);
  }
   
   
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 三十
  • 31
  • 32

(1)电子杂志  *this 所管理资源的指针,这个没什么说的。 
(2)重载  operator*() 操作符,让  *this 有指针的行为。 
(3)重载  operator->() 操作符,让  *this 有指针的行为。 
(4)设置重新  *this 管理的资源,在当然此之前要将  *this 管理的资源释放掉。类似于  operator= 的检查,如果_Ptr 指向的资源就是  *this 管理的资源,就忽略这个操作。否则会提前释放资源。 
(5)  *this 移交出管理权,并将资源的指针返回。因此需要先记录下资源的地址,将然后  *this 指向资源的指针置为空,最后返回资源的地址。

三。总结

auto_ptr 用于RAII(资源获取初始化)思想实现对资源的管理(详情可参考“Effective C ++”Item 13:Use objects to manage resources)。但auto_ptr 属于该思想实现的早期版本,现在的标准库已经不推荐使用该工具了但是,了解。auto_ptr 的功能和实现还是有必要的,其一是,它相当于是其它更复杂智能指针的简化版本,源码简单,容易上手,对后面学习其它智能指针做铺垫; 其二是,学习  auto_ptr 可以让那个我们对RAII思想有所领悟。

四。参考文献

  • Scott Meyers着,侯捷译“Effective C ++”
  • cppreference.com
  • VS2015相关源码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值