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

    【1】左值与右值

    在C语言中,常常会提起左值(lvalue)、右值(rvalue)这样的称呼。

    而在编译程序时,编译器有时也会在报出的错误信息中包含左值、右值的说法。

    不过左值、右值通常不是通过一个严谨的定义而为人所知的。

    事实上,之所以只知道一些关于左值、右值的判断而很少听到其真正的定义的一个原因就是很难归纳。而且即使归纳了,仍需要大量的解释。

    (1)左右值的判别方法一

    大多数时候,左右值的一个最为典型的判别方法:

    在赋值表达式中,出现在等号左边的就是“左值”,而在等号右边的,则称为“右值”。比如:

    a = b + c;

    在这个赋值表达式中,a就是一个左值,而b+c则是一个右值。

    这种识别左值、右值的方法在C++中依然有效。

    (2)左右值判别方法二

    不过C++中还有一个被广泛认同的说法,那就是:

    可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值。

    那么这个加法赋值表达式中,&a是允许的操作,但&(b + c)这样的操作则不会通过编译。

    因此a是一个左值,(b+c)是一个右值。这些判别方法通常都非常有效。

    (3)左右值判别方法三

    更为细致地,在C++11中,右值是由两个概念构成的,一个是纯右值(prvalue,Pure Rvalue),另一个则是将亡值(xvalue,eXpiring Value)。

    其中纯右值就是C++98标准中右值的概念,指的是用于辨识临时变量和一些不跟对象关联的值。

    比如:返回类型为非引用的函数返回的临时变量值就是一个纯右值。

    一些运算表达式,比如1+3产生的临时变量值,也是纯右值。

    而不跟对象关联的字面量值,比如:2、‘c’、true,也是纯右值。

    此外,类型转换函数的返回值、lambda表达式等,也都是右值。

    将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如:

    [1] 返回右值引用T&&的函数返回值

    [2] std::move的返回值

    [3] 转换为T&&的类型转换函数的返回值。

    而剩余的,可以标识函数、对象的值都属于左值

    所以,在C++11的程序中,所有的值必属于左值、将亡值、纯右值三者之一。

    【2】左值引用与右值引用

    在C++11中,右值引用(rvalue reference)就是对一个右值进行引用的类型。

    为了区别于之前C++98中的引用类型,我们称C++98中的引用为“左值引用”(lvalue reference)。

    右值引用和左值引用都是属于引用类型,无论是声明一个左值引用还是右值引用,都必须立即进行初始化。

    其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。

    左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。

    事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。

    通常情况下,我们只能是从右值表达式获得其引用。比如:

    T && a = ReturnRvalue();

    这个表达式中,假设ReturnRvalue返回一个右值,我们就声明了一个名为a的右值引用,其值等于ReturnRvalue函数返回的临时变量的值。

    ReturnRvalue函数返回的右值在表达式语句结束后,其生命也就终结了(通常我们也称其具有表达式生命期)。

    而通过右值引用的声明,该右值又“重获新生”,其生命期将与右值引用类型变量a的生命期一样。

    即就是,只要a还“活着”,该右值临时量将会一直“存活”下去。所以,相比于以下语句的声明方式:

    T b = ReturnRvalue();

    刚才的右值引用变量声明,就会少一次对象的析构及一次对象的构造。

    因为a是右值引用,直接绑定了ReturnRvalue()返回的临时量,而b只是由临时值构造而成的,而临时量在表达式结束后会被析构掉,因应就会多一次析构和构造的开销。

    关于右值引用,可以参考下例加深理解:

     1 #include <iostream>
     2 
     3 void f(int& i) { std::cout << "call left value ref :: " << i << "
    "; }
     4 void f(int&& i) { std::cout << "call right value ref :: " << i << "
    "; }
     5 
     6 int main()
     7 {
     8     int i = 2020;
     9     f(i);      // call left value ref :: 2020
    10     f(1010);   // call right value ref :: 1010
    11     f(std::move(i));  // call right value ref :: 2020
    12 
    13     system("pause");
    14     return 0;
    15 }
    16 
    17 /*
    18 call left value ref :: 2020
    19 call right value ref :: 1010
    20 call right value ref :: 2020
    21 请按任意键继续. . .
    22 */

    【3】常量左值引用 与 常量右值引用

    (1)常量左值引用

    通常情况下,右值引用是不能够绑定到任何的左值的。比如下面的表达式就是无法通过编译的:

    int c;
    int && d = c;

    那么,在C++98标准中就已经出现的左值引用是否可以绑定到右值(由右值进行初始化)呢?比如:

    T & e = ReturnRvalue();
    const T & f = ReturnRvalue();

    这样的语句是否能够通过编译呢?答案是:e的初始化会导致编译时错误,而f则不会。出现这样的状况的原因:

    常量左值引用在C++98标准中开始就是个“万能”的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化。

    而且,在使用右值对其初始化的时候,常量左值引用还可以像右值引用一样将右值的生命期延长。

    不过相比于右值引用所引用的右值,常量左值所引用的右值在它的“余生”中只能是只读的。

    相对地,非常量左值只能接受非常量左值对其进行初始化。

    (2)常量右值引用

    为了语义的完整,C++11中还存在着常量右值引用。比如通过以下代码声明一个常量右值引用:

    const T && crvalueref = ReturnRvalue();

    但是,一来右值引用主要就是为了移动语义,而移动语义需要右值是可以被修改的,那么常量右值引用在移动语义中就没有用武之处;

    二来如果要引用右值且让右值不可以更改,常量左值引用往往就足够了。

    因此,在现在的尴尬情况下,还没有看到常量右值引用有何用处。

    (3)引用类型可引用值的类型表

    下面列出了在C++11中各种引用类型可以引用的值的类型:

    值得注意的是,只要能够绑定右值的引用类型,都能够延长右值的生命期。

    【4】如何判断?

    有的时候,我们可能不知道一个类型是否是引用类型,以及是左值引用还是右值引用(这在模板中比较常见)。

    标准库在<type_traits>头文件中提供了3个模板类:is_rvalue_reference、is_lvalue_reference、is_reference,可供我们进行判断。比如:

     1 #include <iostream>
     2 #include <type_traits>
     3 using namespace std;
     4 
     5 int main()
     6 {
     7     cout << is_reference<int>::value << endl;              // 0
     8     cout << is_rvalue_reference<string &&>::value << endl; // 1
     9     cout << is_lvalue_reference<string &> ::value << endl; // 1
    10     cout << is_rvalue_reference<const string &&>::value << endl; // 1
    11     cout << is_lvalue_reference<const string &> ::value << endl; // 1
    12 }

    我们通过模板类的成员value就可以打印出stirng && 是否是一个右值引用了。

    配合C++11中的类型推导操作符decltype,我们甚至还可以对变量的类型进行判断。

    good good study, day day up.

    顺序 选择 循环 总结

  • 相关阅读:
    信息化与信息系统4
    信息化与信息系统3
    信息化与信息系统2
    信息化与信息系统1
    ASP.NET Core教程:ASP.NET Core 程序部署到Windows系统
    C#几种单例模式
    SQL查询优化
    动态类型dynamic(ExpandoObject)
    PV(访问量)、UV(独立访客)、IP(独立IP)
    Spring Boot 2.配置
  • 原文地址:https://www.cnblogs.com/Braveliu/p/12234824.html
Copyright © 2011-2022 走看看