右值引用与移动语义

右值引用与移动语义

刚开始看这个左值的概念,有点绕。
先提概念,左值是表达式结束后仍然存在的值,右值是值表达式结束后就不在存在的临时值。例如下面代码

int a = fun();

会产生两种类型的值,一类是左值 a,另一类是右值,即fun() 函数返回的一个临时值。如定义那样,这个临时值再赋给a,在表达式结束后被销毁。
再看下面代码

int &&a = fun();

这里对fun() 的返回值,即右值绑定了一个右值引用,那么fun()产生的临时值在表达式结束后不会被销毁了,重获新生,生命周期和左边的右值引用类型变量a一样长。
举个例子

#include <iostream>
using namespace std;
class point{
private:
    int x;
    int y;
public:
    point()
    {
        cout<<"construct"<<endl;
    }
    point(const point &p)
    {
        cout<<"copy construct"<<endl;

    }
    ~point()
    {
        cout<<"destruct"<<endl;
    }

};

point f2(){
    return point();
}
int main() {
    point a = f2();
    return 0;
}

运行结果
在这里插入图片描述
分析如上一篇笔记。
我们这里,仅仅通过右值引用来绑定f2() 函数的返回值,来延长右值的生命周期,进而避免无谓的拷贝和析构过程,即

int main() {
    point &&a = f2(); 
    return 0;
}

运行结果
在这里插入图片描述
可看到,对比之前,在绑定右值引用后,减少了一次临时对象的构造和析构过程。

右值移动实现移动语义,移动语义用来减少拷贝过程中带来的数据负担,对于深拷贝而言,不仅需要拷贝成员变量的数据,还要把成员指针指向的资源一并给拷贝过来。采用移动语义,是将该资源的所有权直接转移过来,并不会发生内存拷贝。其是由移动构造函数实现的。

#include<iostream>
using namespace std;
class point{
private:
    int *x;
public:
    point():x(new int(0))
    {
        cout<<"construct"<<endl;
    }
    point(const point &p):x(new int(*p.x)) //深拷贝构造函数
    {
        cout<<"copy construct"<<endl;

    }
    point(point &&p):x(p.x){    //移动构造函数,这里没有使用深拷贝,只是浅拷贝
        p.x = NULL;
        cout<<"move construct"<<endl;
    }
    ~point()
    {
        cout<<"destruct"<<endl;
    }

};  

point f2(){
    return point();
}
int main() {
    point p1 = f2();
    return 0;
}

运行结果:
在这里插入图片描述
可看到,两个拷贝都是由移动构造函数完成的。可以减少由于深拷贝临时对象带来的性能损失。
为什么会直接匹配到移动构造函数了,而没有匹配到深拷贝构造函数呢?
因为这里 f2() 函数的返回值是一个右值,所以优先匹配移动构造函数,直接采用浅拷贝即可。

std::move()

C++11 标准中为了满足用户使用左值初始化同类对象时也通过移动构造函数完成的需求,新引入了 std::move() 函数,它可以将左值强制转换成对应的右值,由此便可以使用移动构造函数。
再看下面这个例子:

#include<iostream>
using namespace std;

class point{
private:
    int *x;
public:
    point():x(new int(0))
    {
        cout<<"construct"<<endl;
    }
    point(const point &p):x(new int(*p.x))
    {
        cout<<"copy construct"<<endl;

    }
    point(point &&p):x(p.x){
        p.x = NULL;
        cout<<"move construct"<<endl;
    }
    ~point()
    {
        cout<<"destruct"<<endl;
    }

};

int main() {
    point p1;
    point p2 = p1;
输出:
construct
copy construct
destruct
destruct

可采用std 的move函数,改为

int main() {
    point p1;
    point p2 = move(p1);
    return 0;
}
输出:
construct
move construct
destruct
destruct

可以看到,move函数的功能是将左值转换为一个右值引用,从而来使用移动语义,避免深拷贝。

注意,程序提供移动构造函数的同时也会提供一个拷贝构造函数,以防止移动不成功的时候还能拷贝构造。当删除掉类中的移动构造函数时,上述代码 move() 函数会失效但不会报错,编译器找不到移动构造回去找拷贝构造,所以拷贝构造函数 定义为 const point &的形式。


版权声明:本文为qq_35027690原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。