• c++11の的左值、右值以及move,foward


    左值和右值的定义

    在C++中,可以放到赋值操作符=左边的是左值,可以放到赋值操作符右边的是右值。有些变量既可以当左值又可以当右值。进一步来讲,左值为Lvalue,其实L代表Location,表示在内存中可以寻址,可以给它赋值(常量const类型也可以寻址,但是不能赋值),Rvalue中的R代表Read,就是可以知道它的值。例如:
    int a=3;
    a在内存中有地址,而3没有,但是可以read到它的值。
    3=4;
    这个是错误的,因为3的内存中没有地址,不能当作左值。
    下面这个语句不容易出错
    a++=3;
    这个语句编译通不过的,原因在于a++是先使用a的值,再给a加1。实现如下:
    {  
        int tmp=a;  
       a=a+1;  
        return tmp;  
    }  

    ++a是右值。

    ++a=3;
    这个是正确的,++a的实现如下:
    {  
       a=a+1;  
       return &a;  
    }  

    显然++a的效率高。

    左值符号&和右值符号&&

    左值的声明符号为&,右值的声明符号为&&。在C++中,临时对象不能作为左值,但是可以作为常量引用const &
    #include<iostream>  
    void print_lvalue(int& i)//左值  
    {  
        std::cout << "Lvalue:" << i << std::endl;  
    }  
    void print_rvalue(int&& i)//右值  
    {  
        std::cout << "Rvalue:" << i << std::endl;  
    }  
     
    int main()  
    {  
        int i = 0;  
        print_lvalue(i);  
        print_rvalue(1);  
        //print_lvalue(1)会出错  
        //print_lvalue(const int& i)可以使用print_lvalue(1)  
       return 0;  
    }  

    C++11中的move

    有时候我们希望把左值当作右值来使用,例如一个变量的值,不再使用了,希望把它的值转移出去,C++11中的std::move就为我们提供了将左值引用转为右值引用的方法。
    #include<iostream>  
    void print_value(int& i)//左值  
    {  
        std::cout << "Lvalue:" << i << std::endl;  
    }  
    void print_value(int&& i)//右值  
    {  
        std::cout << "Rvalue:" << i << std::endl;  
    }    
    int main()  
    {  
        int i = 10;  
        print_value(i);  
        print_value(std::move(i));  
        return 0;  
    }  

    最长用的交换函数

    void swap(T& a, T& b)  
    {  
        T tmp = std::move(a);  
        a = std::move(b);  
        b = std::move(tmp);  
    }  

    避免了3次拷贝。

    精确值传递

    std::forward主要用于模板编程中,值传递的问题。可以推测参数是左值引用还是右值引用,精确传递值。
     
    std::move和std::forward是C++0x中新增的标准库函数,分别用于实现移动语义和完美转发。
     下面让我们分析一下这两个函数在gcc4.6中的具体实现。
    
    预备知识
    1.引用折叠规则:
    X& + & => X&
     X&& + & => X&
     X& + && => X&
     X&& + && => X&&
    
    2.函数模板参数推导规则(右值引用参数部分):
     当函数模板的模板参数为T而函数形参为T&&(右值引用)时适用本规则。
     若实参为左值 U& ,则模板参数 T 应推导为引用类型 U& 。
     (根据引用折叠规则, U& + && => U&, 而T&& ≡ U&,故T ≡ U& )
     若实参为右值 U&& ,则模板参数 T 应推导为非引用类型 U 。
     (根据引用折叠规则, U或U&& + && => U&&, 而T&& ≡ U&&,故T ≡ U或U&&,这里强制规定T ≡ U )
    
    3.std::remove_reference为C++0x标准库中的元函数,其功能为去除类型中的引用。
    std::remove_reference<U&>::type ≡ U
     std::remove_reference<U&&>::type ≡ U
     std::remove_reference<U>::type ≡ U
    
    4.以下语法形式将把表达式 t 转换为T类型的右值(准确的说是无名右值引用,是右值的一种)
    static_cast<T&&>(t)
    5.无名的右值引用是右值
     具名的右值引用是左值。
    6.注:本文中 ≡ 含义为“即,等价于“。
    
    
    std::move
    
    函数功能
    
    std::move(t) 负责将表达式 t 转换为右值,使用这一转换意味着你不再关心 t 的内容,它可以通过被移动(窃取)来解决移动语意问题。
    
     源码与测试代码
    
    
    [cpp] view plain copy print?
    01.template<typename _Tp>  
    02.  inline typename std::remove_reference<_Tp>::type&&  
    03.  move(_Tp&& __t)  
    04.  { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }  
    
    
    
    
    
    
    [cpp] view plain copy print?
    01.#include<iostream>  
    02.using namespace std;  
    03.  
    04.struct X {};  
    05.  
    06.int main()  
    07.{  
    08.    X a;  
    09.    X&& b = move(a);  
    10.    X&& c = move(X());  
    11.}  
    
    
    
    代码说明
    1.测试代码第9行用X类型的左值 a 来测试move函数,根据标准X类型的右值引用 b 只能绑定X类型的右值,所以 move(a) 的返回值必然是X类型的右值。
    2.测试代码第10行用X类型的右值 X() 来测试move函数,根据标准X类型的右值引用 c 只能绑定X类型的右值,所以 move(X()) 的返回值必然是X类型的右值。
    3.首先我们来分析 move(a) 这种用左值参数来调用move函数的情况。
    4.模拟单步调用来到源码第3行,_Tp&& ≡ X&, __t  ≡ a 。
    
    5.根据函数模板参数推导规则,_Tp&& ≡ X& 可推出 _Tp ≡ X&6.typename std::remove_reference<_Tp>::type ≡ X 。
    typename std::remove_reference<_Tp>::type&& ≡ X&&7.再次单步调用进入move函数实体所在的源码第4行。
    8.static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(a)
    9.根据标准 static_cast<X&&>(a) 将把左值 a 转换为X类型的无名右值引用。
    10.然后我们再来分析 move(X()) 这种用右值参数来调用move函数的情况。
    11.模拟单步调用来到源码第3行,_Tp&& ≡ X&&, __t  ≡ X() 。
    12.根据函数模板参数推导规则,_Tp&& ≡ X&& 可推出 _Tp ≡ X 。
    
    13.typename std::remove_reference<_Tp>::type ≡ X 。
    typename std::remove_reference<_Tp>::type&& ≡ X&&14.再次单步调用进入move函数实体所在的源码第4行。
    15.static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(X())
    16.根据标准 static_cast<X&&>(X()) 将把右值 X() 转换为X类型的无名右值引用。
    17.由9和16可知源码中std::move函数的具体实现符合标准,
     因为无论用左值a还是右值X()做参数来调用std::move函数,
     该实现都将返回无名的右值引用(右值的一种),符合标准中该函数的定义。
    
    std::forward
    
    函数功能
    
    std::forward<T>(u) 有两个参数:T 与 u。当T为左值引用类型时,u将被转换为T类型的左值,否则u将被转换为T类型右值。如此定义std::forward是为了在使用右值引用参数的函数模板中解决参数的完美转发问题。
    
     源码与测试代码
    
    
    [cpp] view plain copy print?
    01./// forward (as per N3143)  
    02.template<typename _Tp>  
    03.  inline _Tp&&  
    04.  forward(typename std::remove_reference<_Tp>::type& __t)   
    05.  { return static_cast<_Tp&&>(__t); }  
    06.  
    07.template<typename _Tp>  
    08.  inline _Tp&&  
    09.  forward(typename std::remove_reference<_Tp>::type&& __t)   
    10.  {  
    11.    static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"  
    12.    " substituting _Tp is an lvalue reference type");  
    13.    return static_cast<_Tp&&>(__t);  
    14.  }  
    
    
    
    
    
    
    [cpp] view plain copy print?
    01.#include<iostream>  
    02.using namespace std;  
    03.  
    04.struct X {};  
    05.void inner(const X&) {cout << "inner(const X&)" << endl;}  
    06.void inner(X&&) {cout << "inner(X&&)" << endl;}  
    07.template<typename T>  
    08.void outer(T&& t) {inner(forward<T>(t));}  
    09.  
    10.int main()  
    11.{  
    12.    X a;  
    13.    outer(a);  
    14.    outer(X());  
    15.    inner(forward<X>(X()));  
    16.}  
    17.//inner(const X&)  
    18.//inner(X&&)  
    19.//inner(X&&)  
    
    
    
    代码说明
    1.测试代码第13行用X类型的左值 a 来测试forward函数,程序输出表明 outer(a) 调用的是 inner(const X&) 版本,从而证明函数模板outer调用forward函数在将参数左值 a 转发给了inner函数时,成功地保留了参数 a 的左值属性。
    2.测试代码第14行用X类型的右值 X() 来测试forward函数,程序输出表明 outer(X()) 调用的是 inner(X&&) 版本,从而证明函数模板outer调用forward函数在将参数右值 X() 转发给了inner函数时,成功地保留了参数 X() 的右值属性。
    3.首先我们来分析 outer(a) 这种调用forward函数转发左值参数的情况。
    4.模拟单步调用来到测试代码第8行,T&& ≡ X&, t  ≡ a 。
    
    5.根据函数模板参数推导规则,T&& ≡ X& 可推出 T ≡ X&6.forward<T>(t) ≡ forward<X&>(t),其中 t 为指向 a 的左值引用。
    
    7.再次单步调用进入forward函数实体所在的源码第4行或第9行。
    
    8.先尝试匹配源码第4行的forward函数,_Tp ≡ X&9.typename std::remove_reference<_Tp>::type ≡ X 。
    typename std::remove_reference<_Tp>::type& ≡ X&10.形参 __t  与实参 t 类型相同,因此函数匹配成功。
    11.再尝试匹配源码第9行的forward函数,_Tp ≡ X&12.typename std::remove_reference<_Tp>::type ≡ X 。
    typename std::remove_reference<_Tp>::type&& ≡ X&&13.形参 __t  与实参 t 类型不同,因此函数匹配失败。
    14.由10与13可知7单步调用实际进入的是源码第4行的forward函数。
    15.static_cast<_Tp&&>(__t) ≡ static_cast<X&>(t) ≡ a。
    16.inner(forward<T>(t)) ≡ inner(static_cast<X&>(t)) ≡ inner(a) 。
    17.outer(a) ≡ inner(forward<T>(t)) ≡ inner(a)
    再次单步调用将进入测试代码第5行的inner(const X&) 版本,左值参数转发成功。
    18.然后我们来分析 outer(X()) 这种调用forward函数转发右值参数的情况。
    19.模拟单步调用来到测试代码第8行,T&& ≡ X&&, t  ≡ X() 。
    20.根据函数模板参数推导规则,T&& ≡ X&& 可推出 T ≡ X 。
    
    21.forward<T>(t) ≡ forward<X>(t),其中 t 为指向 X() 的右值引用。
    
    22.再次单步调用进入forward函数实体所在的源码第4行或第9行。
    23.先尝试匹配源码第4行的forward函数,_Tp ≡ X 。
    24.typename std::remove_reference<_Tp>::type ≡ X 。
    typename std::remove_reference<_Tp>::type& ≡ X&25.形参 __t  与实参 t 类型相同,因此函数匹配成功。
    26.再尝试匹配源码第9行的forward函数,_Tp ≡ X 。
    27.typename std::remove_reference<_Tp>::type ≡ X 。
    typename std::remove_reference<_Tp>::type&& ≡ X&&28.形参 __t  与实参 t 类型不同,因此函数匹配失败。
    29.由25与28可知22单步调用实际进入的仍然是源码第4行的forward函数。
    
    30.static_cast<_Tp&&>(__t) ≡ static_cast<X&&>(t) ≡ X()。
    31.inner(forward<T>(t)) ≡ inner(static_cast<X&&>(t))  ≡ inner(X())。
    32.outer(X()) ≡ inner(forward<T>(t)) ≡ inner(X())
    再次单步调用将进入测试代码第6行的inner(X&&) 版本,右值参数转发成功。
    
    33.由17和32可知源码中std::forward函数的具体实现符合标准,
     因为无论用左值a还是右值X()做参数来调用带有右值引用参数的函数模板outer,
     只要在outer函数内使用std::forward函数转发参数,
     就能保留参数的左右值属性,从而实现了函数模板参数的完美转发。
  • 相关阅读:
    Alfred上可提高工作效率的Workflow推荐
    局部性原理——各类优化的基石
    持续学习——程序猿的军备竞赛
    http://regex.alf.nu/ 非标准答案
    13总结
    Ubuntu下python安装mysqldb(驱动)
    北大ACM试题分类+部分解题报告链接
    poj 3253 Fence Repair(优先队列+huffman树)
    Centos/Fedora下安装Twisted,failed with error code 1 in /tmp/pip-build-H1bj8E/twisted/解决方法
    关于command 'gcc' failed with exit status 1 解决方法
  • 原文地址:https://www.cnblogs.com/xietianjiao/p/6626157.html
Copyright © 2020-2023  润新知