c++ primer 学习笔记之右值引用

  • 注:当我们尝试用不具名对象构建新对象的时候编译器会用NRVORVO优化

    c++返回值优化的手段:

     

    1RVONRVO

    RVO =Return value optimization

    中文意思是不具名返回值优化

    NRVO  = Named Return value optimization

    中文意思是具名返回值优化

    这两种都是编译器在汇编级别上对代码段的优化,对于c++程序员来说却是透明的。

     

    多说无益,直接上一个例子,这是从网上摘下来的一段代码:

     

    #include<iostream>

    using namespace std;

    class RVO

    {

        friend RVO operator +(RVO & a, RVO& b);

    public:

     

    RVO(){printf("Iam in constructor\n");}

    RVO(const RVO& c_RVO) {printf ("I am in copy constructor\n");}

    ~RVO(){printf("I am in destructor\n");}

    intmem_var;

    };

    RVO operator +(RVO& a, RVO & b)

    {

        std::cout<<" +\n";

        RVO c;

        c.mem_var = a.mem_var + b.mem_var;

        return c;

    }

    RVO foo()

    {

    RVOrvo;

    returnrvo;

    }

    RVO foo1()

    {

        return RVO();

    }

    int main()

    {

    RVOrvo= RVO();

    return0;

    }

    我想对于大多数学过c++的人也知道,常理来说的打印结果是这样的

     


    于是我开启了编译器的返回值优化

     


     

    优化结果就是少了一次拷贝。

     

    对于RVO rvo= RVO();

    编译器可能做出这样的优化

    rvo.RVO::RVO();

    于是乎就相当于直接构造了rvo

     

    改一下主函数:

    int main()

    {

    RVOrvo= foo();

    return0;

    }

     

    关闭优化后,查看输出:

     


     

    打开优化:

     


     

    直接少了两次拷贝

    一般对于上述情况的优化可能是这样的

    RVOfoo(RVO &_hiddenArg,RVO &var)

    {

    _hiddenArg.RVO::RVO();

    Return;

    }

     

     

    计算机生成了可选文字:RVO r RVO(O) + RVO(I) + RVO(2);

    对于上述式子,没错,依旧可以被优化

    +(+(RVO(0), RVO(1)), RVO(2))

    没优化之前:
    参数从左到右入栈,于是:

     


    优化后:

     


    一般返回不具名变量,编译器都会主动去优化。

    • 2、右值引用

       

      右值,也可以说是一个不具名对象。我们可以用一个const引用引用右值,但是右值引用却不能引用左值。

      还有一点值得注意的是,不是我们引用了右值,右值的生命就会变得持久,一个语句结束后,右值会被马上释放。我们唯一能做的就是看能否在马上消失的右值那里窃取一些好处

       

      用《深入理解C++11:C++11新特性解析与应用》里面的一段话

      还有一个与移动语义看似无关,但偏偏有些关联的话题是,编译器中被称为RVO/NRVO的优化(RVO, Return Value Optimization,返回值优化,或者NRVO,Named Return Value optimization)。事实上,在本节中大量的代码都使用了-fno-elide-constructors选项在g++/clang++中关闭这个优化,这样可以使读者在代码中较为容易地利用函数返回的临时量右值。

      但若在编译的时候不使用该选项的话,读者会发现很多构造和移动都被省略了。对于下面这样的代码,一旦打开g++/clang++的RVO/NRVO,从ReturnValue函数中a变量拷贝/移动构造临时变量,以及从临时变量拷贝/移动构造b的二重奏就通通没有了。

       

      1. A ReturnRvalue() { A a(); return a; }  
      2. b = ReturnRvalue(); 

      b变量实际就使用了ReturnRvalue函数中a的地址,任何的拷贝和移动都没有了。通俗地说,就是b变量直接“霸占”了a变量。这是编译器中一个效果非常好的一个优化。不过RVO/NRVO并不是对任何情况都有效。比如有些情况下,一些构造是无法省略的。还有一些情况,即使RVO/NRVO完成了,也不能达到最好的效果。但结论是明显的,移动语义可以解决编译器无法解决的优化问题,因而总是有用的。

      确实,本来我想用一个右值去匹配拷贝构造函数,但是编译器的优化让我们直接去调用了构造函数,拷贝构造函数被搁置在了一边。确定一个右值的重载函数有没有被调用,关键在于看编译器是否有优化:

      #include <iostream>

      using namespace std;

      class RVO

      {

      public:

       

      RVO(){printf("I am in constructor\n");}

      RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}

      ~RVO(){printf ("I am in destructor\n");}

      int mem_var;

      };

      void MyMethod (RVO& rvo)

      {

          std::cout<<" Mythod copy\n";

      }

      void MyMethod (RVO&& rvo)

      {

       std::cout<<" Mythod rv\n";

      }

       

      int main()

      {

          RVO rvo;

      MyMethod(rvo);

      MyMethod(RVO());

      return 0;

      }

       

      很明显,这只是简单的左值和右值引用,并没有产生新的对象,于是这里没有优化

      判断是否会使用优化,我们应该看他是否构成了拷贝。

       


      于是乎,我们是不是可以在传入右值的时候做点优化

      #include <iostream>

      using namespace std;

      class RVO

      {

      public:

       

      RVO(){mem_var = new int(0);printf("I am in constructor\n");}

      RVO (const RVO& c_RVO) {

          mem_var = new int(*c_RVO.mem_var);

          printf ("I am in copy constructor\n");

          }

      RVO (RVO&& c_RVO) {

          mem_var = c_RVO.mem_var;

          c_RVO.mem_var = nullptr ;

          printf ("I am in copy constructor\n");}

      ~RVO(){

          delete mem_var;

          mem_var  = nullptr;

          printf ("I am in destructor\n");

          }

      int *mem_var;

      };

      void MyMethod (RVO& rvo)

      {

          std::cout<<" Mythod copy\n";

      }

      void MyMethod (RVO&& rvo)

      {

       std::cout<<" Mythod rv\n";

      }

       

      int main()

      {

          RVO rvo;

      MyMethod(rvo);

      MyMethod(RVO());

      return 0;

      }

      既然右值可以构成重载,那么就让编译器去判断左右值吧,我们只需要去关心拿到右值后我们该做些什么,我只知道右值生命不长久,那么在他结束生命前我是否从他那里得到一些好处,来减小自己的开销。注意,如果我们觉得一个具名对象我们不再需要他了,我们就用std::move()告诉编译器,我觉得把这个变量作为右值。但是在使用这个方法前,你必须保证你已经清楚自己在右值重载的函数里对右值做了些什么,而且被转换的变量应该在之后都不会去使用它。

       

      接下来看看几种情况会使RVONRVO失效

      1、不同的返回路径上返回不同名的对象(比如if XXX 的时候返回x,else的时候返回y)

      2、引入 EH 状态的多个返回路径(就算所有的路径上返回的都是同一个具名对象)

      3、在内联asm语句中引用了返回的对象名。

      4、在函数内,用用std::move()包围返回值

       

      #include <iostream>

      using namespace std;

      class RVO

      {

      public:

       

      RVO(){mem_var = new int(0);printf("I am in constructor\n");}

      RVO (const RVO& c_RVO) {

          mem_var = new int(*c_RVO.mem_var);

          printf ("I am in copy constructor\n");

          }

      RVO (RVO&& c_RVO) {

          mem_var = c_RVO.mem_var;

          c_RVO.mem_var = nullptr ;

          printf ("I am in copy constructor\n");}

      ~RVO(){

          delete mem_var;

          mem_var  = nullptr;

          printf ("I am in destructor\n");

          }

      int *mem_var;

      };

      void MyMethod (RVO& rvo)

      {

          std::cout<<" Mythod copy\n";

      }

      void MyMethod (RVO&& rvo)

      {

       std::cout<<" Mythod rv\n";

      }

      RVO foo()

      {

      RVO rvo;

          return std::move(rvo);

      }

      int main()

      {

      RVO r = foo();

      return 0;

      }

      就是说上面的 return std::move(rvo);不仅不会提高效率,而是会造成一次不必要的拷贝,因为他使得NRVO失效了

       

      #include <iostream>

      using namespace std;

      class RVO

      {

      public:

      RVO(){mem_var = new int(0);printf("I am in constructor\n");}

      RVO (const RVO& c_RVO) {

          mem_var = new int(*c_RVO.mem_var);

          printf ("I am in copy constructor\n");

          }

      RVO (RVO&& c_RVO) {

          mem_var = c_RVO.mem_var;

          c_RVO.mem_var = nullptr ;

          printf ("I am in copy constructor\n");}

      ~RVO(){

          delete mem_var;

          mem_var  = nullptr;

          printf ("I am in destructor\n");

          }

      int *mem_var;

      };

      void MyMethod (RVO& rvo)

      {

          std::cout<<" Mythod copy\n";

      }

      void MyMethod (RVO&& rvo)

      {

       std::cout<<" Mythod rv\n";

      }

      RVO foo()

      {

          return std::move(RVO());

      }

      int main()

      {

      RVO rvo;

      MyMethod(std::move(rvo));

      return 0;

      }

      不过在上面的这种情况下,并没有任何的值拷贝,所以不会有RVO(仅仅是对右值和局部变量有优化),我们可以用move语句去告诉编译器我以后再也不会去用rvo这个变量了。

       

      3、模板推导右值引用

       


      当你传入一个右值的时候,如int(),那么推导出的是,int

      但是,如果你传入一个左值,但是他的确不会报错,这是很让人吃惊的事情,其实对于左值,他推倒出的T却是一个int&

       

      上面的例子,我在外部传入一个左值,并在内部定义了一个T类型的变量,并将其赋值与t,之后改变其值,那么不出所料,那么外部的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值