当前位置: 首页 > 专题 > 正文

C++ | 右值引用与std::move()

2023-08-03 21:10:15博客园

右值引用

  • 左值:可以被取地址的变量或值

  • 右值:无法被修改,无法取地址的值。一般为临时变量。


    (资料图片仅供参考)

  • 左值引用:

    • 常引用,只能指向左值

    • 或者通过 const的方式指向一个右值

      const int & a = 17;

      所以,函数形参定义为const type & var时,既可以接受变量,也可以接受右值。如std::vectorpush_back()

      void push_back(const type & val);

      如果没有const,那v.push_back(17)这样的代码就无法编译通过了。

  • 右值引用: 只能指向右值的引用,指向左值则无法通过编译

    • 使用右值引用,本质上将一个右值变成了一个左值:int &&a = 17;

      变量a是一个左值,所以右值引用是一个左值

    • 对于函数形参而言,定义为type && var将只接受为右值的实参。

    为什么会有右值引用的出现呢?
    // 类Obj数据的定义class Obj{    int _size;    char *_buf;};

    当我们定义Obj类的构造函数时,如果我们想用对象a来初始化对象b,有时候初始化后我们还想要继续使用对象a,那这时候的构造函数就需要将对象a的数据完全复制给对象b

    Obj(const Obj &o){    _size = o._size;    _buf = new char[_size];    for(int i=0;i<_size;i++) _buf[i] = o._buf[i];}
    (这样称为深拷贝。浅拷贝是简单地复制值,包括指针,经过浅拷贝之后的两个指针只是指向了同一个地址;而深拷贝会在堆内存中另外申请空间给新指针或者新引用)

    而有的时候对象a只是一个临时变量(右值),用完即弃。这时候直接将对象a的数据移动给对象b即可:

    Obj(const Obj &o){    _size = o._size;    _buf =  o._buf;    o._buf = nullptr;}
    (注意到,在移动构造函数中,末尾有o._buf = nullptr。之所以要将被移动数据的对象中的指针/引用置为空指针,原因是避免在对象a和对象b的析构函数中都对该地址进行delete

    但实际上,以上的移动构造函数是无法实现的,因为const使得传入对象无法被修改。因此,为了实现移动构造函数,C++11引入了右值引用:

    Obj(Obj && o){    _size = o._size;    _buf =  o._buf;    o._buf = nullptr;}

    这样问题就得到解决了。当构造函数时传入参数为右值时,会调用Obj(Obj && o);若是左值,则会调用Obj(const Obj & o)

    此时,又有另外一个问题,如果使用一个对象a是一个对象而非一个临时变量,但我依旧想要使用移动数据的方式来构造函数(对象a在之后不再使用),那该怎么办呢?

    如果依旧使用Obj b(a)来构造,那还是会复制构造。要使用移动构造,就得使用某种方法将对象a变成右值。而函数std::move()就起到这个作用:

    std::move(var) --- 作用是类型转换:接受一个左值作为参数,返回其右值引用

    所以此时使用 Obj b(std::move(a)),就用移动构造初始化了对象b

    很多类的成员函数实际上都实现了这两种方法,比如vector的push_back():
    // std::vector方法定义void push_back(const type & value);void push_back(type && value);vector vs;string str = "hello world";vs.push_back(str);             // 此时是传入左值,push_back会深拷贝strvs.push_back(std::move(str)); // 此时传入了右值,push_back浅拷贝str作为vs中的元素                     // 同时str被置为空,不能再被使用

关键词: