C++11的右值引用

好久没有写文章了,一是因为最近公司项目比较紧,二是在看c++,工作两年多c++的知识都忘记了,今天就来简单总结一下c++11的右值引用。
提前声明,本文参考了从4行代码看右值引用

1.右值引用要解决的问题

c++11引入右值引用要解决C++98/03中遇到的两个问题,第一个问题就是临时对象非必要的昂贵的拷贝操作,第二个问题是在模板函数中如何按照参数的实际类型进行转发。
在c++11中所有的值分为三种类型:左值、将亡值、纯右值。纯右值是非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式等;将亡值是将要被移动的对象、T&&函数返回值、std::move返回值和转换为T&&的类型的转换函数的返回值等。

2.几个尝试性的测试

测试基本代码如下:

#include <iostream>
using namespace std;

int g_contr_cnt = 0;
int g_decontr_cnt = 0;
int g_copy_cnt = 0;

class A {
public:
    A() {
        cout << "construct: " << ++g_contr_cnt << endl;
    }

    A(const A& oth) {
        cout << "copy: " << ++g_copy_cnt << endl;
    }

    ~A() {
        cout << "deconstruct: " << ++g_decontr_cnt << endl;
    }
};

A getVal() {
    return A();
}
测试1:值拷贝
A x = getVal();

由于编辑器会对代码进行优化,因此我们在编译上面的代码的时候,加入-fno-elide-constructors,运行结果如下:

construct: 1
copy: 1
deconstruct: 1
copy: 2
deconstruct: 2
deconstruct: 3

调用了两次复制构造函数,一次在返回值,一次在对x的赋值。如果A类的复制构造函数不是简简单单地打印信息,而是有大量内存的拷贝,那怎么办?两次复制构造函数性能开销就会很大,并且关键的是拷贝源实例是一个将亡值,留下它根本没有什么用。

换一种方案:const A&

const A& x = getVal();

这时候输出结果如下:

construct: 1
copy: 1
deconstruct: 1
deconstruct: 2

但const reference是不能修改引用的内容的!

另一个方案:右值引用

A&& x = getVal();

这样输出结果与上面一种方案是一样的,并且x并不是一个const类型。右值引用将返回的值“续命”了。

测试2:移动构造函数

我们现在类的定义中加入移动构造函数:

A(A&& oth) {
    cout << "move: " << ++g_move_cnt << endl;
}

这样子,我们再看一下输出结果:

A x = getVal();

输出结果如下:

construct: 1
move: 1
deconstruct: 1
move: 2
deconstruct: 2
deconstruct: 3

没有拷贝构造函数了,与之代替的是移动构造函数,移动构造函数的实现,我们可以认为移动源实例是一个将亡值,这样我们在移动构造函数的内部实现中只需要将移动源实例move到新的实例中即可。

测试3:参数A&&

void setVal(A&&);
void setVal(const A&);

这两个函数,编译器是怎样判断的呢?做了一些尝试:

A x;
setVal(x);   // 左值
setVal(std::move(x));   // 右值
setVal(A());   // 右值
A&& y = getVal();
setVal(y);  // 左值

测试4:std::move

std::move是c++11提供移动实例的方式,测试代码如下:

A x;
A y = std::move(x);

y的初始化使用了移动构造函数:

construct: 1
move: 1
deconstruct: 1
deconstruct: 2

如果类A没有移动构造函数,将会使用复制构造函数。