右值引用
右值引用是C++11中的一个重要特性,它的出现解决了很多C++历史遗留问题。其总结来说可以说是解决了栈中对象的所有权的转移问题。
首先右值是从C语言设计时就有的概念。不严格的来说,左值对应变量的存储位置,而右值对应变量的值本身。C++中右值可以被赋值给左值或者绑定到引用。类的右值是一个临时对象,如果没有被绑定到引用,在表达式结束时就会被废弃。于是我们可以在右值被废弃之前,移走它的资源进行废物利用,从而避免无意义的复制,就像函数传参的时候,如果传入的是一个右值,就没有必要再去拷贝一份临时对象。被移走资源的右值在废弃时已经成为空壳,析构的开销也会降低。
C++ 11中用&表示左值引用,用&&表示右值引用,如:
int b = 5;
int &&a1 = 10;//10为一个右值。
int &&a2 = b+10;//b+10为临时变量,是右值。
int a1 = b;//这里可以这样用,说明右值引用变量a1是一个左值。
右值引用,是对临时对象的一种引用,它是在初始化时完成引用的,但是右值引用不代表引用临时对象后就不能改变右值引用所引用对象的值,仍然可以在初始化后改变临时对象的值。
通俗来说就是把临时对象(一般不能引用)的资源移动到其他地方去。也就是说扩展了临时对象的生命周期。
使用实例
比如有一个Vector类
class Vector {
public:
Vector(int len, int initial_value)
: len_(len)
{
elements_ = new int[len];
memset(elements_, initial_value, len * sizeof(int));
}
Vector(const Vector& v)
: len_(v.len_)
{
elements_ = new int[len];
memcpy(elements_, v.elements_, len * sizeof(int));
}
private:
int *elements_;
int len_;
};
这时如果我们要写一个函数,返回一个在栈区分配空间的Vector对象:
Vector ConstructVector(int len)
{
return Vector(len, 0);
}
Vector v = ConstructVector(1000000);
这样操作调用一次构造函数,2次深拷贝。但是函数中return后面的对象是个Vector临时对象,何必对它做深拷贝呢?于是在C++ 11中,我们为 Vector类定义转移构造函数:
Vector(Vector &&v)
: len_(len)
{
elements_ = v.elements_;
v.elements_ = NULL;
}
这样当我们调用ConstructVector时就不会触发深拷贝了,只是把对象转移到了你所构造的空间。
std::move()
函数传参的时候,值传递通常参数会对其进行拷贝一份临时的进行传递,并不是直接把值传过去,但是最符合人类思维方式的是直接传值到函数内部。而且如果是直接传值会减少很多不必要的栈空间开销。这个时候move语义的出现就解决了这个问题。如果确定某个值是一个非常量右值(或者是一个以后不会再使用的左值),则我们在进行临时对象的拷贝时,可以不用拷贝实际的数据,而只是“窃取”指向实际数据的指针。
move的作用就是把左值转换成右值,因为当你确定这个左值不再使用之后,就没有必要再对他进行拷贝等操作之后再去传给函数。
void TestSTLObject()
{
std::string str = "Hello";
std::vector<std::string> v;
v.push_back(str);
std::cout << "After copy, str is \"" << str << "\"\n";
v.push_back(std::move(str));
std::cout << "After move, str is \"" << str << "\"\n";
std::cout << "The contents of the vector are \"" << v[0]
<< "\", \"" << v[1] << "\"\n";
}
输出为
After copy, str is "Hello"
After move, str is ""
The contents of the vector are "Hello", "Hello"
原lvalue值被moved from之后值被转移,所以为空字符串。