好久没有写文章了,一是因为最近公司项目比较紧,二是在看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没有移动构造函数,将会使用复制构造函数。