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

来源:互联网 发布:淘宝男装原创店铺 编辑:程序博客网 时间:2024/05/16 17:45

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

  • auto_ptr是STL中的智能指针家族的成员之一, 它管理由new expression获得的对象,在auto_ptr对象销毁时,他所管理的对象也会自动被delete` 掉。
  • auto_ptr的拷贝构造函数和拷贝赋值会改变 right hand value,并且拷贝的副本不会等于原始的、被拷贝的那个auto_ptr对象的值。(实际上,auto_ptr` 的拷贝构造函数和拷贝赋值函数会让 left hand value 接管 right 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>;

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) 构造 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.

If you notice, the conversion operator in auto_ptr that automatically converts to an auto_ptr_ref does a release on the source auto_ptr, just like the copy constructor does.

It’s kind of a weird little dance that happens behind the scenes because auto_ptr modifies the thing being copied from.

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

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

 ~auto_ptr(); // deprecated

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)

auto_ptr 的拷贝赋值函数会让 left hand value 接管 right hand value 所管理的对象。

5. 隐式类型转换函数

    //deprecated...    template<class Y>                     (1)    operator auto_ptr_ref<Y>();    template<class Y>                     (2)    operator auto_ptr<Y>();

(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) 返回该 *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;}
  • 运行结果:

    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    };

这个辅助类的源码比较简单, 没有什么说的。前面也分析过了,这个辅助类其实是为了帮助 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) {}

其中, 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; 

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

3. auto_ptr 析构函数的源码

  ~auto_ptr()  { // destroy the object    delete _Myptr;  }

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);  }

这里涉及到解决“自我赋值”(assignment to self)的问题,详情请参阅《Effective C++》 Item 11: Handle assignment to self in operator=。
(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) auto_ptrauto_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) 获取 *this 所管理资源的指针, 这个没什么说的。
(2) 重载 operator*() 操作符,让 *this 有指针的行为。
(3) 重载 operator->() 操作符,让 *this 有指针的行为。
(4) 重新设置 *this 管理的资源, 当然在此之前要将 *this 管理的资源释放掉。类似于 operator= 的检查,如果_Ptr 指向的资源就是 *this 管理的资源,就忽略这个操作。否则会提前释放资源。
(5) *this 移交出管理权,并将资源的指针返回。因此需要先记录下资源的地址,然后将 *this 指向资源的指针置为空,最后返回资源的地址。

三. 总结

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

四. 参考文献

  • Scott Meyers 著, 侯捷译《Effective C++》
  • cppreference.com
  • VS2015 相关源码

五. 推荐阅读

  • 关于右值: cppreference.com关于值类型的详细解读:lvalue,rvalue,xvalue,prvalue,glvalue
  • 关于右值引用: C++11移动语义探讨——从临时对象到右值引用
1 0
原创粉丝点击