• 【C++】右值引用


    右值的概念

    左值、右值通常不是通过一个严谨的定义而为人所知的,大多数时候左右值的定义与其判别方法是一体的。一个最为典型的判别方法就是,在赋值表达式中,出现在等号左边的就是“左值”,而在等号右边的,则称为“右值”
    更为细致地,在C++11中,右值是由两个概念构成的,一个是将亡值(xvalue,expiring Value),另一个则是纯右值(prvalue,Pure Rvalue)。
    纯右值是C++98标准中右值的概念,用于辨识临时变量和一些不跟对象关联的值

    • 非引用返回的函数返回的临时变量值
    • 运算表达式
    • 类型转换函数的返回值
    • lambda表达式

    将亡值则是C++11新增的跟右值引用相关的表达式,通常是将要被移动的对象

    • 返回右值引用T&&的函数返回值
    • std∶move的返回值
    • 可以标识函数、对象的值

    右值引用

    T && a = ReturnRvalue ();
    
    • 1

    ReturnRvalue 函数返回的右值在表达式语句结束后,其生命也就终结了,而通过右值引用的声明,该右值又"重获新生",其生命期将与右值引用类型变量 a的生命期一样。只要 a还"活着",该右值临时量将会一直"存活"下去。

    相比于以下语句的声明方式∶

    T b = ReturnRvalue();
    
    • 1

    通过右值引用的方式,少一次对象的析构以及一次对象的构造。a是右值引用直接绑定ReturnRvalue的返回值,b由临时值够成,临时值会经历一次构造和析构。

    右值引用是不能够绑定到任何的左值的
    C++98左值引用不能绑定右值,常量左值引用可以接受左值、常量左值、右值初始化

    在这里插入图片描述

    && 的特性

    C++ 中,并不是所有情况下 && 都代表是一个右值引用,具体的场景体现在模板和自动类型推导中

    • 函数模板中使用 && 需要通过传入的实参来确定参数 param 的实际类型。

      template<typename T>
      void f(T&& param);
      void f1(const T&& param);
      
      //对于 f(10) 来说传入的实参 10 是右值,因此 T&& 表示右值引用
      f(10); 	
      int x = 10;
      
      //对于 f(x) 来说传入的实参是 x 是左值,因此 T&& 表示左值引用
      f(x); 
      
      //f1(x) 的参数是 const T&& 不是未定引用类型,不需要推导,本身就表示一个右值引用
      f1(x);
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    • 如果是模板参数,需要指定为 T&&,如果是自动类型推导,需要指定为 auto &&,在这两种场景下 && 被称作未定的引用类型。

      int main()
      {
          int x = 520, y = 1314;
          //auto&& 表示一个整形的左值引用
          auto&& v1 = x;
          //auto&& 表示一个整形的右值引用
          auto&& v2 = 250;
          //decltype(x)&& 等价于 int&& 是一个右值引用不是未定引用类型,y 是一个左值,不能使用左值初始化一个右值引用类型。
          decltype(x)&& v3 = y;   // error
          cout << "v1: " << v1 << ", v2: " << v2 << endl;
          return 0;
      };
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

    移动语义

    在 C++11中,拷贝/移动构造函数实际上有以下3个版本∶

    T Object(T &)
    T Object(const T &)
    T Object(T &&)
    
    • 1
    • 2
    • 3
    • 常量左值引用的版本是一个拷贝构造版本
    • 右值引用版本是一个移动构造版本
    • 通常情况下,如果需要移动语义,程序员必须自定义移动构造函数

    在这里插入图片描述
    在这里插入图片描述

    完美转发

    完美转发(perfect forwarding),是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。

    template <typename T>
    void IamForwording (T t) {  IrunCodeActually(t); }
    
    • 1
    • 2

    IrunCodeActually是执行代码的目标函数,希望转发函数将参数按照传入 lamforwarding时的类型传递。传入lamForwording 的是右值对象,IrunCodeActually就能获得右值对象)。

    在上面例子中,使用了最基本类型进行转发,该方法会导致参数在传给 IrunCodeActually 之前就产生了一次额外的临时对象拷贝。因此这样的转发只能说是正确的转发,但谈不上完美。

    C++11是如何解决完美转发的问题

    C++11是通过引入一条所谓**"引用折叠”(reference collapsing)** 的新语言规则,并结合新的模板推导规则来完成完美转发

    typedef const int T;
    typedef T& TR; 
    TR& v = 1; // 该声明在C++98中会导致编译错误
    
    • 1
    • 2
    • 3

    在C++11中,一旦出现了这样的表达式,就会发生引用折叠,即将复杂的未知表达式折叠为已知的简单表达式

    在这里插入图片描述
    一旦定义中出现了左值引用引用折叠总是优先将其折叠为左值引用
    转发函数的实参是类型X的一个左值引用,那么模板参数被推导为X&类型
    转发函数的实参是类型X的一个右值引用,那么模板参数被推导为X&&类型

    template <typename T>
    void IamForwording(T && t) { 
    	IruncodeActually(static_cast<T &&>(t));
    }
    
    • 1
    • 2
    • 3
    • 4

    这里的static_cast是留给传递右值用的

    void IamForwording(X& && t) {
    	IruncodeActually(static_cast<X& &&>(t)); 
    }
    
    //应用上引用折叠规则,就是 -->>
    
    void IamForwording(X& t) {
    	IrunCodeActually(static_cast<X&>(t));
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    调用转发函数时传入了一个X类型的右值引用的话,我们的转发函数将被实例化为:

    void IamForwording(X&& && t) { 
    	IrunCodeActually(static_cast<X&& &&>(t));
    }
    
    //应用上引用折叠规则,就是 -->>
    
    void IamForwording(X&& t) {
    	IrunCodeActually(static_cast<X&&>(t));
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    static_cast的重要性: 将传入参数转换为右值。std∶move通常就是一个static_cast。
    在C++11中,用于完美转发的函数叫做 forward

    template <typename T>
    void IamForwording(T && t) {
    	IrunCodeActually(forward(t));
    }
    
    • 1
    • 2
    • 3
    • 4

    【实现完美转发的例子】

    #include 
    using namespace std;
    template <typename T,typename U>
    void PerfectForward (T &&t,U& Func){
    	cout << t <<"\tforwarded..."<< endl;
    	Func (forward<T>(t));
    }
    void RunCode (double && m) {}
    void RunHome (double && h) {}
    void RunComp (double && c) {}
    int main(){ 
    	PerfectForward(1.5,RunComp); //1.5 forwarded...
    	PerfectForward(8,RunCode);// 8 forwarded... 
    	PerfectForward (1.5,RunHome);// 1.5 forwarded...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    线性表的概念
    数据结构-----【链表:基础】
    “ONE众一心”,华为云Marketing as a Service加速伙伴市场飞轮
    反向传播详解BP
    KubeEdge 边缘计算应用部署
    WPF布局控件之StackPanel布局
    软考 -结构化开发
    软考高级系统架构设计师系列之:系统架构设计案例分析常考知识点
    【MySQL】内置函数——数学函数+其他函数
    【C# 窗体 超市购物买单系统】简单版和进阶版
  • 原文地址:https://blog.csdn.net/vhcjgc/article/details/126362299