zoukankan      html  css  js  c++  java
  • C++11右值引用

    何为右值?

    下面两行代码中i是左值,10是右值;v是左值,getVar()返回的是个临时变量,也是右值,改临时变量在改行表达式结束后消失。

    1 int i = 10;
    2 int &j = getVar();
    3 auto f = []{return 6;};

    左值的特征,我的理解是能取地址的是左值,不能取指,生存周期仅限于某个表达式的是右值,如上面的10,我们是取不到地址的,因为该行表达式结束后变量即被释放,即使取址了也没有意义,后面没法利用这个地址在操作这个值了。右值是无名临时变量。

    C++中函数返回的非引用临时变量、lamda表达式、运算表达式产生的临时变量都属于纯右值。

    Why右值引用?

    就是对右值的引用咯。废话。。。写法是T &&a=b;

    1 int&& i = 10;    //i是对10的右值引用

    C++11为何要有右值引用呢?为了解决C++98的临时变量的拷贝问题,OBJ的拷贝开销是比较大的。

    很多时候有这样一个场景:一个函数要返回一个对象,或对象的引用,但这个对象是临时的,因此就不能返回引用了,只能返回OBJ,对临时变量进行拷贝,如下所示:

    1 T getObj(void) 
    2 {
    3     T OBJ;
    4     //do sth with OBJ
    5     return OBJ;
    6 }

    右值引用很好的解决了这个问题。比较下面两行代码:

    1 T obj= getObj();    //调用了T的拷贝构造函数,成本高
    2 T &&objk= getObj();   //对返回对象的右值引用

    上面这两行代码的唯一差别就在于前面的&&,但是语意上相差很大:第一行是熟悉的,会调用拷贝构造函数,而第二行是对返回的临时对象的右值拷贝,会对临时对象进行续命,这个临时对象和objk的声明周期是一致的。用了右值引用就减少了一次临时变量的拷贝(拷贝给返回值),一次临时变量的析构。

    T&&一定是右值吗?

    看如下一段代码:

    1 void post(int &&t) {}
    2 
    3 int main()
    4 {
    5     post(10);    //OK
    6 
    7     int &&x = 10;
    8     post(x);  //Error, x是左值,需要传右值。
    9 }

    上述代码确实让人认为只能传右值给post函数作为入参。但是当发生自动推导,也就是模版或者auto时,情况就不一样了:

     1 template <typename T>
     2 void post(T &&t) {}
     3 
     4 int main()
     5 {
     6     post(10);     //OK 
     7 
     8     int &&x = 10;
     9     post(x);   //OK
    10 }

    T&& t在发生自动类型推断的时候,它是未定的引用类型(universal references)(个人认为是左值引用或者右值引用类型,虽然写了T&&),如果被一个左值初始化,它就是一个左值;如果它被一个右值初始化,它就是一个右值,它是左值还是右值取决于它的初始化。

    Why右值引用2.0

    有这样一个场景,某个函数返回临时变量OBJ,按照C++98的做法是调用拷贝构造函数,注意:如果OBJ内有指针,肯定是要深拷贝的,也就是说要重新new出一个堆来。但是对临时变量的深拷贝完成后,马上就释放了堆内存,特别是对于内存占用比较大的OBJ,不仅多余,而且严重影响性能。C++11的右值引用很好的解决了这个问题。C++11为了解决这个问题,提出了move constructor的概念,也就是移动构造函数。

     1 class A
     2 {
     3 public:
     4     A() :m_ptr(new int(0)){}
     5     A(const A& a):m_ptr(new int(*a.m_ptr)) //深拷贝的拷贝构造函数
     6     {
     7         cout << "copy construct" << endl;
     8     }
     9     A(A&& a) :m_ptr(a.m_ptr)       // move constructor
    10     {
    11         a.m_ptr = nullptr;
    12         cout << "move construct" << endl;
    13     }
    14     ~A(){ delete m_ptr;}
    15 private:
    16     int* m_ptr;
    17 };
    18 
    19 A GetA()
    20 {
    21     return A();
    22 }
    23 
    24 int main()
    25 {
    26     A a = GetA();
    27 }

    上面的代码并没有调用拷贝构造函数进行深拷贝,而是调用了move constructor,简单的将指针的所有权交给了新的对象,之前的指针变为null。

    1 A(A&& a) :m_ptr(a.m_ptr)
    2 {
    3     a.m_ptr = nullptr;
    4     cout << "move construct" << endl;
    5 }

    这里怎么知道要调用move constructor而不是copy constructor的呢?这里并没发生类型推导,因为都是明确的类型。所以入参必须是右值,否则编译报错。函数返回值属于右值,所有调用了move constructor了。试问:左值也想要使用move constructor呢?C++11提供了std::move,将左值转换为右值,便于使用move constructor。需要注意的一个细节是,我们提供移动构造函数的同时也会提供一个拷贝构造函数,以防止移动不成功的时候还能拷贝构造,使我们的代码更安全。

    std::move

    1 {
    2     std::list< std::string> tokens;
    3     //省略初始化...
    4     std::list< std::string> t = tokens; //这里存在拷贝 
    5 }
    6 std::list< std::string> tokens;
    7 std::list< std::string> t = std::move(tokens);  //这里没有拷贝 

    如果不用std::move,拷贝的代价很大,性能较低。std::move避免了不必要的深拷贝。

    std::move只是将左值左了cast_to_rvalue操作然后显式的调用移动构造函数,对于右值,直接使用std::move显式调用移动构造函数。并不是所有情况用std::move都好的,比如int和char[10]定长数组等类型,使用move的话仍然会发生拷贝,因为他没有对应的移动构造函数。对于含有句柄或是指针的对象(也就是需要深拷贝的对象),std::move操作更有意义。说到这里,个人认为右值引用的根本目的就是减少不必要的深拷贝,而不是避免拷贝。对于浅拷贝,右值引用意义不大。

    std::forward<T>

    之前说到过,模版自动推导时或者auto时T&&接收的参数不一定时右值,也有可能时右值,原本型参是左值的,传进来后也变成右值了。如下实验:

    void proccessValue(int &i)
    {
        cout<<"rvalue"<<endl;
    }
    
    void proccessValue(int &&i)
    {
        cout<<"lvalue"<<endl;
    }
    
    template <typename T>
    void forwardValue(T &&i)
    {
    
        proccessValue(i);   //i变成了右值,不管传进来之前是左还是右
    }
    
    int main()
    {
        int i = 10;
        forwardValue(10);
        forwardValue(i);
    } 
    

     这就造成了后续使用的困惑了。为了保持型参原来的左右属性,使用std::forward<T>

    1 template <typename T>
    2 void forwardValue(T &&i)
    3 {
    4 
    5     proccessValue(std::forward<T>(i));
    6 }

    overall

    右值引用解决了无谓的拷贝构造函数的开销。

    移动构造函数(move constructor)告诉编译器如何进行拷贝右值对象,避免了深拷贝的开销。

    左值也想使用右值引用的特性,所有C++11提供了std::move函数,将左值cast_to_rvalue了。

    类型推导时传给T&&后导致左值变成了右值,此时想利用其原始的左值属性的话,C++11又提供了std::forward<T>()。

    本文也参考了:从4行代码看右值引用

  • 相关阅读:
    使用winmm.dll 获取麦克风声音数据
    什么是拆箱和装箱?
    C#窗体程序【用户控件-窗体】委托事件
    如何在网页标题栏加入logo图标?
    C#汉字转拼音帮助类
    JQuery中$.ajax()方法参数详解
    UEditor独立图片、文件上传模块
    SqlServer2008安装时提示重启计算机失败 解决办法
    如果说人生是自我编写的程序
    LINQ的Any() 方法
  • 原文地址:https://www.cnblogs.com/howo/p/9091266.html
Copyright © 2011-2022 走看看