• 为什么需要auto_ptr_ref


    auto_ptr:唯一拥有权

    智能指针(智能指针是一个对象)保证,无论在何种情形下,只要自己被摧毁,就一定连带释放其所指资源。而由于智能型指针本身就是区域变量,所以无论是正常退出,还是异常退出,只要函数退出,它就一定会被摧毁。

    只有auto_ptr可以拿来当做另一个auto_ptr 的初值,普通指针是不行的(因为没有一个自动类型转换,可以将普通指针转换成智能指针——根据一般指针生成一个auto_ptr的那个构造函数,被声明为explicit。explicit的用法参见相关内容)。

    std::auto_ptr<ClassA> ptr;     // create an auto_ptr
    ptr = new ClassA;     // Error
    ptr = std::auto_ptr<ClassA>(new ClassA);       // OK, delete old object and own new

    牢记:auto_ptr的语义本身就包含了拥有权,所以如果你无意转交你的拥有权,就不要在参数列中使用auto_ptr,也不要以它作为返回值。原因如下:

    (1)作为参数(实参,函数调用过程中传入)的auto_ptr会将拥有权转交给参数p(形参,函数定义中,假设为p)——“值传递”,而当函数退出时,会删除参数p所拥有的对象.

    (2)以pass by reference传递的话,面对一个“透过by reference 而获得auto_ptr”的函数,你根本无法预知拥有权是否被转交.

    解决方法(如果非要将auto_ptr当做参数传递的话):

    (1)可以运用constant reference,避免了拥有权转交给参数p的问题;

    (2)将auto_ptr定义为constant,如 “const std::auto_ptr<int> p(new int);”,避免auto_ptr在函数内被无意修改以致所有权转移问题;

    这一方案使得auto_ptr比显得更安全一些。事实上,C++标准程序库的所有容器都如此,如:

    template <class T>
    void container::insert (const T& value)  // constant reference
    {
        ...
        x = value;         // 
        ...
    };

    总之,为了避免拥有权的意外转移,如果你的auto_ptr在整个生命周期内都不必改变其所指对象的拥有权,你可以使用const auto_ptr。

    auto_ptr不满足STL容器对其元素的要求,因为在拷贝和赋值动作之后,原本的auto_ptr和新产生的auto_ptr并不相等。拷贝和赋值之后,原本的auto_ptr会交出拥有权,而不是拷贝给新的auto_ptr(原auto_ptr交出拥有权,变成null指针了)。因此请绝对不要将auto_ptr作为标准容器的元素

    例外:我们不应该以任何形式传递auto_ptr,但当面对output操作符的时候,我们可以将auto_ptr以const reference的方式传递(只作简单输出行为,不会修改)。

    在auto_ptr构造函数“explicit auto_ptr::auto_ptr(T* ptr) throw() ”中:

    (1)构造完成后,*this成为ptr所指对象的唯一拥有者。不允许再有其他拥有者;

    (2)如果ptr本身不是null指针,那就必须是个new返回值,因为auto_ptr析构函数会对其所拥有的对象自动调用delete;

    (3)不能用new[] 所生成的array作为初值。因为auto_ptr是调用delete删除对象,而不是调用delete[]。

    注意:auto_ptr的copy构造函数和assignment 函数被设计为转交auto_ptr所有权。

    “auto_ptr::auto_ptr(auto_ptr& ap) throw()”,

    “template<class U>auto_ptr::auto_ptr(auto_ptr<U>& ap) throw()”。

    针对non-const values而设计的一个copy构造函数,生成一个auto_ptr,在入口处将ap所拥有的对象的拥有权夺取过来,ap变为null指针。这个操作改变了原对象。此函数有一个重载的member template,使得ap可通过型别自动转换,构造出合适的auto_ptr。例如,根据一个“派生类的对象”,构造出一个基类对象的auto_ptr.

    auto_ptr最后一个内容涉及左值/右值,引进了auto_ptr_ref类别,使我们得以拷贝和赋值non-const auto_ptrs(包括临时对象),却不能拷贝和赋值const auto_ptrs。详细解释如下:

     --------------------------------------------------------------------------------------------------------

    为什么需要auto_ptr_ref

    auto_ptr的拥有权 :

    C++常见的智能指针有std::auto_ptr、boost::shared_ptr、boost::scoped_ptr、boost::shared_array、boost::scoped_array等。auto_ptr只是其中一种而已。但是,为什么auto_ptr才有auto_ptr_ref ,而boost::shared_ptr却没有shared_ptr_ref呢?答案与auto_ptr的特性有关。auto_ptr强调对资源的拥有权 (ownership)。也就是说,auto_ptr是"它所指对象"的拥有者。而一个对象只能属于一个拥有者,严禁一物二主,否则就是重婚罪,意料外的灾难将随之而来。

    为了保证auto_ptr的拥有权唯一,auto_ptr的拷贝构造函数和赋值操作符做了这样一件事情:移除另一个auto_ptr的拥有权 。为了说明拥有权的转移 ,请看下面的代码示例:

    // ----------------------------------------------------------------------------
    #include <iostream>   
    #include <memory>   
    using namespace std;   
      
    int main(int argc, char **argv){   
        auto_ptr<int> ptr1(new int(1));   
        auto_ptr<int> ptr2(ptr1); //ptr1的拥有权被转移到ptr2   
    
        auto_ptr<int> ptr3(NULL);   
        ptr3 = ptr2;                //ptr2的拥有权被转移到ptr3   
    
        cout<<ptr1.get()<<endl;     //结果为0   
        cout<<ptr2.get()<<endl;     //结果为0   
        cout<<*ptr3<<endl;          //结果为1  
    }
    
    // --------------------------------------------------------------------------

    auto_ptr的拷贝构造函数与赋值操作符  
    由于需要实现拥有权的转移,auto_ptr的拷贝构造函数和赋值操作符,与一般类的做法不太相同。我们可以看看MinGW5.1.6实现的auto_ptr源代码:

    // --------------------------------------------------------------------------
    /**  
    *  @brief  An %auto_ptr can be constructed from another %auto_ptr.  
    *  @param  a  Another %auto_ptr of the same type.  
    *  
    *  This object now @e owns the object previously owned by @a a,  
    *  which has given up ownsership.  
    */  
    auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) {}   
      
    /**
    *  @brief  %auto_ptr assignment operator.  
    *  @param  a  Another %auto_ptr of the same type.  
    *  
    *  This object now @e owns the object previously owned by @a a,  
    *  which has given up ownsership.  The object that this one @e  
    *  used to own and track has been deleted.  
    */  
    auto_ptr&   
    operator=(auto_ptr& __a) throw () {   
        reset(__a.release());   
        return *this;  
    }

    可以看到,auto_ptr的拷贝构造函数、赋值操作符,它们的参数都是auto_ptr& (为了转交所有权,),而不是const auto_ptr &
    一般来说,类的拷贝构造函数和赋值操作符的参数都是const &。但是auto_ptr的做法也是合理的:确保拥有权能够转移
    如果auto_ptr的拷贝构造函数和赋值操作符的参数是const auto_ptr &, 那么实参的拥有权将不能转移。因为转移拥有权需要修改auto_ptr的成员变量,而实参确是一个const对象,不允许修改。

    右值与const & :
    假设我们想写出下面的代码:

    // --------------------------------------------------------------------------
    #include <iostream>   
    #include <memory>   
    using namespace std;   
    
    int main(int argc, char **argv) {   
        auto_ptr<int> ptr1(auto_ptr<int>(new int(1)));  //使用临时对象进行拷贝构造   
        auto_ptr<int> ptr2(NULL);   
        ptr2 = (auto_ptr<int>(new int(2)));           //使用临时对象进行赋值   
    }

    假设没有定义auto_ptr_ref类及相关的函数,那么这段代码将不能通过编译。主要的原因是,拷贝构造函数及赋值操作符的参数:auto_ptr<int>(new int(1))和 auto_ptr<int>(new int(2)) 都是临时对象 。临时对象属于典型的右值 ,而非const &是不能指向右值的(就是说给引用赋值) (参见《More Effective C++》 ,Item 19)。auto_ptr的拷贝构造函数及赋值操作符的参数类型恰恰是auto_ptr&,明显 非const &。  

    左值和右值:

    左值可以出现在赋值语句的左边或右边。

    右值只能出现在赋值语句的右边。(一般是临时变量、函数的非引用返回值,参见C++易混淆知识点整理 第2点)

    例如 x*y是个右值,编译表达式x*y=10;则出现错误.

    非const引用不能绑定右值

    同理,下面的两段代码,也不会通过编译:

    // --------------------------------------------------------------------------
    #include <iostream>   
    #include <memory>   
    using namespace std;   
    auto_ptr<int> f();   
    int main(int argc, char **argv) {   
        auto_ptr<int> ptr3(f());  //使用临时对象进行拷贝构造   
        auto_ptr<int> ptr4(NULL);   
        ptr4 = f();               //使用临时对象进行赋值   
    }  
      
    // --------------------------------------------------------------------------
    #include <iostream>   
    #include <memory>   
    using namespace std;   
    auto_ptr<int> f(){   
        return auto_ptr<int>(new int(3));  //这里其实也使用临时对象进行拷贝构造   
    }  

    普通类不会遇到这个问题,是因为他们的拷贝构造函数及赋值操作符(不管是用户定义还是编译器生成的版本),参数都是const &。
    auto_ptr_ref之目的
    传说当年C++标准委员会的好多国家,因为这个问题都想把auto_ptr从标准库中剔除。好在Bill Gibbons和Greg Colvin创造性地提出了auto_ptr_ref,解决了这一问题,世界清静了。
    auto_ptr_ref之原理
    很显然,下面的构造函数,是可以接收auto_ptr临时对象的。

    // 传值,而不是传引用,可以接收临时对象 
    auto_ptr(auto_ptr __a) throw() : _M_ptr(__a.release()) { }  

    但另一个问题也很显然:上述构造函数不能通过编译。如果能通过编译,就会陷入循环调用(为什么?谁看了之后理解的留下评论,谢谢)。我们稍作修改:

    // --------------------------------------------------------------------------
    auto_ptr(auto_ptr_ref<element_type> __ref) throw()  //element_type就是auto_ptr的模板参数。   
        : _M_ptr(__ref._M_ptr) { }   

    该版本的构造函数,可以接收auto_ptr_ref的临时对象。如果auto_ptr可以隐式转换到auto_ptr_ref,那么我们就能够用auto_ptr临时对象来调用该构造函数。这个隐式转换不难实现:

    // 类的类型转换运算符,在auto_ptr类里面实现,目的:将auto_ptr 隐式转换为auto_ptr_ref<_Tp1>类型 --------------------------------------------------------------------------
    template<typename _Tp1>   
            operator auto_ptr_ref<_Tp1>() throw()   
            { return auto_ptr_ref<_Tp1>(this->release()); }  

    附录1:SGI STL 中auto_ptr的实现:

    至此,我们可以写出下面的代码,并可以通过编译:

    // --------------------------------------------------------------------------
    #include <iostream>   
    #include <memory>   
    using namespace std;   
      
    int main(int argc, char **argv) {   
        auto_ptr<int> ptr1(auto_ptr<int>(new int(1)));  //由于是临时对象,所以不会调用到“auto_ptr(auto_ptr& __a)”,而是调用auto_ptr_ref版本的构造函数   
    }  

    同理,如果我们再提供下面的函数:

    // --------------------------------------------------------------------------
    auto_ptr&   
        operator=(auto_ptr_ref<element_type> __ref) throw()   
    {   
        if (__ref._M_ptr != this->get())   
        {   
            delete _M_ptr;   
            _M_ptr = __ref._M_ptr;   
        }   
        return *this;   
    }  

    那么,下面的代码也可以通过编译:

    // --------------------------------------------------------------------------
    #include <iostream>   
    #include <memory>   
    using namespace std;   
      
    int main(int argc, char **argv) {   
        auto_ptr<int> ptr2(NULL);   
        ptr2 = (auto_ptr<int>(new int(2)));  //调用auto_ptr_ref版本的赋值操作符   
    }

    auto_ptr_ref之本质
    本质上,auto_ptr_ref赋予了auto_ptr“引用”的语义 。

    附录1:SGI STL 中auto_ptr的实现

    #ifndef __SGI_STL_MEMORY
    #define __SGI_STL_MEMORY
    
    #include <stl_algobase.h>
    #include <stl_alloc.h>
    #include <stl_construct.h>
    #include <stl_tempbuf.h>
    #include <stl_uninitialized.h>
    #include <stl_raw_storage_iter.h>
    
    
    __STL_BEGIN_NAMESPACE
    
    #if defined(__SGI_STL_USE_AUTO_PTR_CONVERSIONS) && 
        defined(__STL_MEMBER_TEMPLATES)
    
    template<class _Tp1> struct auto_ptr_ref {      // 定义auto_ptr_ref类型
    _Tp1
    * _M_ptr; auto_ptr_ref(_Tp1* __p) : _M_ptr(__p) {} }; #endif template <class _Tp> class auto_ptr { // 定义auto_ptr
    p
    rivate: _Tp* _M_ptr; public: typedef _Tp element_type; // 构造函数,原始指针类型参数 explicit auto_ptr(_Tp* __p = 0) __STL_NOTHROW : _M_ptr(__p) {} // 拷贝构造函数,接收非const引用类型参数
    auto_ptr(auto_ptr
    & __a) __STL_NOTHROW : _M_ptr(__a.release()) {} #ifdef __STL_MEMBER_TEMPLATES // 模板 拷贝构造函数
    template
    <class _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) __STL_NOTHROW : _M_ptr(__a.release()) {} #endif /* __STL_MEMBER_TEMPLATES */ auto_ptr& operator=(auto_ptr& __a) __STL_NOTHROW { if (&__a != this) { delete _M_ptr; _M_ptr = __a.release(); } return *this; } #ifdef __STL_MEMBER_TEMPLATES template <class _Tp1> auto_ptr& operator=(auto_ptr<_Tp1>& __a) __STL_NOTHROW { if (__a.get() != this->get()) { delete _M_ptr; _M_ptr = __a.release(); } return *this; } #endif /* __STL_MEMBER_TEMPLATES */ ~auto_ptr() __STL_NOTHROW { delete _M_ptr; } _Tp& operator*() const __STL_NOTHROW { return *_M_ptr; } _Tp* operator->() const __STL_NOTHROW { return _M_ptr; } _Tp* get() const __STL_NOTHROW { return _M_ptr; } _Tp* release() __STL_NOTHROW { _Tp* __tmp = _M_ptr; _M_ptr = 0; return __tmp; } void reset(_Tp* __p = 0) __STL_NOTHROW { if (__p != _M_ptr) { delete _M_ptr; _M_ptr = __p; } } // According to the C++ standard, these conversions are required. Most // present-day compilers, however, do not enforce that requirement---and, // in fact, most present-day compilers do not support the language // features that these conversions rely on. #if defined(__SGI_STL_USE_AUTO_PTR_CONVERSIONS) && defined(__STL_MEMBER_TEMPLATES) public: // 接收auto_ptr_ref<_Tp>类型参数,或者接收auto_ptr临时对象类型参数(隐式转换成auto_ptr_ref<_Tp>类型)
    auto_ptr(auto_ptr_ref
    <_Tp> __ref) __STL_NOTHROW : _M_ptr(__ref._M_ptr) {} auto_ptr& operator=(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW { if (__ref._M_ptr != this->get()) { delete _M_ptr; _M_ptr = __ref._M_ptr; } return *this; }  // 定义 auto_ptr类型转换成auto_ptr_ref类型的 类型转换运算符(可以实现隐式转换,因为没有使用关键字explicit)
    template
    <class _Tp1> operator auto_ptr_ref<_Tp1>() __STL_NOTHROW { return auto_ptr_ref<_Tp1>(this->release()); } template <class _Tp1> operator auto_ptr<_Tp1>() __STL_NOTHROW { return auto_ptr<_Tp1>(this->release()); } #endif /* auto ptr conversions && member templates */ }; __STL_END_NAMESPACE #endif /* __SGI_STL_MEMORY */ // Local Variables: // mode:C++ // End:
  • 相关阅读:
    算法初步——哈希表B.1038统计同成绩学生
    算法初步——哈希表B10133.旧键盘打字 (注意bool型数组的赋值为true的方法)
    算法初步——哈希表B1029/A1084. 旧键盘
    算法初步——排序 A1012.The Best Rank(25)
    《思维导图》——东尼博赞
    算法初步——排序B1015/A1062.德才论
    入门模拟——(字符串处理)A1001. A+B Format(20)
    RMQ问题(线段树+ST算法)
    PKU 2406 Power Strings(KMP最长循环不重叠字串)
    KMP算法 kuangbin
  • 原文地址:https://www.cnblogs.com/yyxt/p/5012569.html
Copyright © 2020-2023  润新知