如果有小伙伴看到我的文章的话,建议优先去看下面的这些文章,因为我写的质量不及下面文章的十分之一(哭脸)

还有这些视频:

lvalue and rvalue

在C++中,每一个表达式都是由lvalue和rvalue组成.

Example

int i = 0;    /*i为lvalue,0为rvalue*/
int a = i+1;        /*a为lvalue,i+1是rvalue*/
1        /*1为rvalue*/

lvalue和rvalue具体的定义是什么?

实际上lvalue和rvalue很难给出一个具体的定义,但是我们可以给出简单的,概括的,应对大部分情况的说明。

  • lvalue : 一个对象在内存中占据了可识别位置的,有地址的,持久的(脱离了单个表达式语句还依然存在的).
    • 例如: 上面提到的int a = i+1,程序运行到了下一条语句a依然存在内存中为,&a是合法的,lvalue,i+1则不存在内存中,不是持久的,&(i+1)是不合法的,所以为rvalue。
  • rvalue : 表达式中任何不是lvalue的对象就是rvalue.

下面解释出自Lei Mao的博客lvalue VS rvalue, rvalue Reference, and Move Semantics in C++:

lvalue expression is associated with a specific piece of memory, the lifetime of the associated memory is the lifetime of lvalue expression, and we could get the memory address of it. rvalue expression might or might not take memory. Even if an rvalue expression takes memory, the memory taken would be temporary and the program would not usually allow us to get the memory address of it.

Reference

一个变量的引用可以理解为一个变量的别名,我们可以通过这个别名来实际操作这个变量。

lvalue Reference (左值引用)

T& 表示lvalue Reference , lvalue Reference能绑定lvalue和rvalue(例外)。

Example

int i = 1;    
int& a = i;        /*a为变量i的lvalue Reference*/
int& b = (a+1)        /*Error! , (a+1)为rvalue*/
a = 2;            /*a==2 ; i==2*/
i = 3;            /*a==3;i==3*/
int const& c = 20;    /*例外:这样可以做到将一个rvalue绑定到一个lvalue reference上*/

rvalue Reference (右值引用)

T&& 用来表示rvalue Reference , rvalue Reference只能绑定rvalue。

Example

int a = 1;
int && b = a+1;    /*b为rvalue Reference*/

rvalue Reference 常常用于 Move SemanticsPerfect Forward

Move Semantics

我们先引入一段代码再做介绍:

#include <stdio.h>
#include <algorithm>
#include <cstring>
class String
{
    char* data;

public:

    /*Command Constructor*/
    String(const char* p)
    {
    printf("Command Constructor!\n");
        size_t size = std::strlen(p) + 1;
        data = new char[size];
        std::memcpy(data, p, size);
    }
   ~String()
    {
    printf("Destoryed!\n");
        delete[] data;
    }
    /*Copy Constructor*/
    String(const String& that)
    {
    printf("Copy Constructor!\n");
        size_t size = std::strlen(that.data) + 1;
        data = new char[size];
        std::memcpy(data, that.data, size);
    }
    /*Move Constructor*/
    String(String&& that)   // string&& is an rvalue reference to a string
    {
    printf("Move Constructor!\n");
        data = that.data;
        that.data = nullptr;
    }
    char* getData(){
        return data;
    }
    void printfString(){
        printf("%s\n",data);
    }
};

class student {
    private:
        String name;
    public:
        student(const String& s):name(s){}
        //student(String&& s):name((String&&) s){}
    void printfName(){
        name.printfString();
    }
};

int main(){
    student a("LiQiBin");
    a.printfName();
    return 0;
}

通常情况下我们想创建一个student类,我们调用函数student a("LiQiBin"),函数首先会调用String类中的Command Constructor来创建一个传入参数,传入参数为lvalue Reference,虽然LiQiBin是一个rvalue但是会被转化为lvalue Reference,参照上文lvalue Reference的介绍,然后当student的构造器执行到name(s)时,程序又会调用String类中的Copy Constructor,最后才得到完整的student参数。

输出如下:

Command Constructor!
Copy Constructor!
Destoryed!
LiQiBin
Destoryed!

这个过程中分别调用了String类的Command ConstructorCopy Constructor这就意味着执行了两次内存的分配(new操作)和复制memcpy()操作,String类中的字符数非常少还可以接受,如果被操作的String类内的字符数量极其庞大,两次内存的分配和复制就是对资源相当大的浪费! 是否有办法,只执行一次复制就可以构造出student类呢?

答案是右值引用(rvalue Reference)的Move Semantics

我们取消代码中的student(String&& s):name((String&&) s){}这行注释,这样rvalueLiQiBin就会优先被解释为rvalue Reference,程序就会先执行String类中的Command Constructor构造出一个传入参数,然后执行name((String&&)s),即执行String类中的Move Constructor

下面是Move Constructor的代码:

    /*Move Constructor*/
    String(String&& that)   // string&& is an rvalue reference to a string
    {
        printf("Move Constructor!\n");
        data = that.data;
        that.data = nullptr;
    }

可以看到Move Constructor没有调用newmemcpy而只是将rvalue Reference中的that.data指向的地址偷来给自己用,再将that.data指向nullptr,因为rvalue是暂时的,不持久的参数,student a("LiQiBin")执行结束后生成的临时String类就会调用析构函数将得到的资源收回,所以将that.data指向nullptr就避免了,临时String类收回资源时将指向LiQiBin位置收回,就相当于Move Constructorrvalue Reference中的资源偷来,成功只用一次newmemcpy就构建出了student类。

输出如下:

Command Constructor!
Move Constructor!
Destoryed!
LiQiBin
Destoryed!


C/C++      C/C++ Reference lvalue rvalue

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!