zoukankan      html  css  js  c++  java
  • 为什么需要auto_ptr_ref

    为什么需要auto_ptr_ref

    这几天开始拜读侯捷先生和孟岩先生的译作《C++标准程序库:自修教程与参考手册》 。两位先生确实译功上乘,读得很顺。但是读到P55页关于auto_ptr_ref的讨论,却百思不得其解:为什么需要引入auto_ptr_ref这个辅助类呢?

    从书中描述来看,仿佛与拷贝构造函数右值类型转换 有关。于是,结合auto_ptr的源代码,google之、baidu之,找了一推资料,终于初步 搞清该问题。

    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的拥有权 。为了说明拥有权的转移 ,请看下面的代码示例:

    Cpp代码 

    1. #include <iostream>   
    2. #include <memory>   
    3. using namespace std;   
    4.   
    5. int main(int argc, char **argv){   
    6.     auto_ptr<int> ptr1(new int(1));   
    7.     auto_ptr<int> ptr2(ptr1); //ptr1的拥有权被转移到ptr2   
    8.   
    9.     auto_ptr<int> ptr3(NULL);   
    10.     ptr3 = ptr2;                //ptr2的拥有权被转移到ptr3   
    11.   
    12.     cout<<ptr1.get()<<endl;     //结果为0   
    13.     cout<<ptr2.get()<<endl;     //结果为0   
    14.     cout<<*ptr3<<endl;          //结果为1  

    #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源代码:

    Cpp代码 

    1.  /**  
    2.  *  @brief  An %auto_ptr can be constructed from another %auto_ptr.  
    3.  *  @param  a  Another %auto_ptr of the same type.  
    4.  *  
    5.  *  This object now @e owns the object previously owned by @a a,  
    6.  *  which has given up ownsership.  
    7.  */  
    8. auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) {}   
    9.   
    10. /**  
    11.  *  @brief  %auto_ptr assignment operator.  
    12.  *  @param  a  Another %auto_ptr of the same type.  
    13.  *  
    14.  *  This object now @e owns the object previously owned by @a a,  
    15.  *  which has given up ownsership.  The object that this one @e  
    16.  *  used to own and track has been deleted.  
    17.  */  
    18. auto_ptr&   
    19. operator=(auto_ptr& __a) throw () {   
    20.     reset(__a.release());   
    21.     return *this;   
    22. }  

     /**

     *  @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& ,而不是auto_ptr const & 。

        一般来说,类的拷贝构造函数和赋值操作符的参数都是const &。但是auto_ptr的做法也是合理的:确保拥有权能够转移

        如果auto_ptr的拷贝构造函数和赋值操作符的参数是auto_ptr const & ,那么实参的拥有权将不能转移。因为转移拥有权需要修改auto_ptr的成员变量,而实参确是一个const对象,不允许修改。

     

    右值与const &

    假设我们想写出下面的代码:

    Cpp代码 

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

    #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 &。

     左值和右值

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

    右值只能出现在赋值语句的右边。

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

    const引用不能绑定右值

    2011-9-3 16:44

    提问者: 古刃霜剑 | 浏览次数:78次

    为什么 const &a=40是对的,而&a=40是错的,其中具体的原理是怎样的?
    &a是对a解引用了,是a的地址,当然不能把40复给他
    而const必须在声明时初始化,也就是const &a=40是初始化而不是把40给赋值,所以编译能通过

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

    Cpp代码 

    1. #include <iostream>   
    2. #include <memory>   
    3. using namespace std;   
    4. auto_ptr<int> f();   
    5. int main(int argc, char **argv) {   
    6.     auto_ptr<int> ptr3(f());  //使用临时对象进行拷贝构造   
    7.     auto_ptr<int> ptr4(NULL);   
    8.     ptr4 = f();               //使用临时对象进行赋值   
    9. }  

    #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();       //使用临时对象进行赋值

    }

    Cpp代码 

    1. #include <iostream>   
    2. #include <memory>   
    3. using namespace std;   
    4. auto_ptr<int> f(){   
    5.     return auto_ptr<int>(new int(3));  //这里其实也使用临时对象进行拷贝构造   
    6. }  

    #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临时对象的。

    Cpp代码 

    1. auto_ptr(auto_ptr __a) throw() : _M_ptr(__a.release()) { }  

    auto_ptr(auto_ptr __a) throw() : _M_ptr(__a.release()) { }

     

        但另一个问题也很显然:上述构造函数不能通过编译。如果能通过编译,就会陷入循环调用。我们稍作修改:

    Cpp代码 

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

    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临时对象来调用该构造函数。这个隐式转换不难实现:

    Cpp代码 

    1. template<typename _Tp1>   
    2.         operator auto_ptr_ref<_Tp1>() throw()   
    3.         { return auto_ptr_ref<_Tp1>(this->release()); }  

    template<typename _Tp1>

            operator auto_ptr_ref<_Tp1>() throw()

            { return auto_ptr_ref<_Tp1>(this->release()); }

     

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

    Cpp代码 

    1. #include <iostream>   
    2. #include <memory>   
    3. using namespace std;   
    4.   
    5. int main(int argc, char **argv) {   
    6.     auto_ptr<int> ptr1(auto_ptr<int>(new int(1)));  //调用auto_ptr_ref版本的构造函数   
    7. }  

    #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_ref版本的构造函数

    }

     

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

    Cpp代码 

    1. auto_ptr&   
    2.      operator=(auto_ptr_ref<element_type> __ref) throw()   
    3.      {   
    4. if (__ref._M_ptr != this->get())   
    5.   {   
    6.     delete _M_ptr;   
    7.     _M_ptr = __ref._M_ptr;   
    8.   }   
    9. return *this;   
    10.      }  

     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;

          }

     

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

    Cpp代码 

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

    #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“引用”的语义,这一点可以从auto_ptr_ref的注释看出:

    Cpp代码 

    1. /**  
    2.    *  A wrapper class to provide auto_ptr with reference semantics.  
    3.    *  For example, an auto_ptr can be assigned (or constructed from)  
    4.    *  the result of a function which returns an auto_ptr by value.  
    5.    *  
    6.    *  All the auto_ptr_ref stuff should happen behind the scenes.  
    7.    */  
    8.   template<typename _Tp1>   
    9.     struct auto_ptr_ref   
    10.     {   
    11.       _Tp1* _M_ptr;   
    12.          
    13.       explicit  
    14.       auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }   
    15.     };  

    /**

       *  A wrapper class to provide auto_ptr with reference semantics.

       *  For example, an auto_ptr can be assigned (or constructed from)

       *  the result of a function which returns an auto_ptr by value.

       *

       *  All the auto_ptr_ref stuff should happen behind the scenes.

       */

      template<typename _Tp1>

        struct auto_ptr_ref

        {

          _Tp1* _M_ptr;

         

          explicit

          auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }

        };

     

    auto_ptr_ref之代码

    这里列出auto_ptr_ref相关的函数,共参考:

    Cpp代码 

    1. auto_ptr(auto_ptr_ref<element_type> __ref) throw()   
    2. : _M_ptr(__ref._M_ptr) {}   
    3.   
    4. auto_ptr&   
    5. operator=(auto_ptr_ref<element_type> __ref) throw () {   
    6.     if (__ref._M_ptr != this->get()) {   
    7.         delete _M_ptr;   
    8.         _M_ptr = __ref._M_ptr;   
    9.     }   
    10.     return *this;   
    11. }   
    12.   
    13. template<typename _Tp1>   
    14. operator auto_ptr_ref<_Tp1>() throw () {   
    15.     return auto_ptr_ref<_Tp1> (this->release());   
    16. }   
    17.   
    18. template<typename _Tp1>   
    19. operator auto_ptr<_Tp1>() throw () {   
    20.     return auto_ptr<_Tp1> (this->release());   
    21. }  
  • 相关阅读:
    Java参数传递方式
    C++成员函数的 重载、隐藏、覆盖分析(转)
    回调函数 (一)
    Java之String 专题二
    从10亿个浮点数中找出最大的1万个
    【onclick事件】【改变 HTML 内容innerHTML】【图片替换】【改变标签的css】【判断输入是否是数字】
    【页面加载】【九九乘法表】【document.write的功能_】【<script>直接显示数组】【声明新变量】
    Windows10 环境下安装 ElasticSearch
    数据包和数据报有何区别?
    NIO 通道和缓冲区
  • 原文地址:https://www.cnblogs.com/skyofbitbit/p/2681687.html
Copyright © 2011-2022 走看看