右值引用是一种行为与普通引用 X& 非常相似的类型,但有几个例外。最重要的是,当涉及到函数重载决议时,左值更喜欢旧式左值引用,而右值更喜欢新的右值引用:
void foo(X& x); // lvalue reference overload
void foo(X&& x); // rvalue reference overload
X x;
X foobar();
foo(x); // argument is lvalue: calls foo(X&)
foo(foobar()); // argument is rvalue: calls foo(X&&)
那么什么是右值?任何不是左值的东西。一个左值存在
一个引用内存位置的表达式,并允许我们通过 & 运算符获取该内存位置的地址。
首先通过一个例子来理解右值的作用几乎更容易:
#include <cstring>
class Sample {
int *ptr; // large block of memory
int size;
public:
Sample(int sz=0) : ptr{sz != 0 ? new int[sz] : nullptr}, size{sz}
{
if (ptr != nullptr) memset(ptr, 0, sz);
}
// copy constructor that takes lvalue
Sample(const Sample& s) : ptr{s.size != 0 ? new int[s.size] :\
nullptr}, size{s.size}
{
if (ptr != nullptr) memcpy(ptr, s.ptr, s.size);
std::cout << "copy constructor called on lvalue\n";
}
// move constructor that take rvalue
Sample(Sample&& s)
{ // steal s's resources
ptr = s.ptr;
size = s.size;
s.ptr = nullptr; // destructive write
s.size = 0;
cout << "Move constructor called on rvalue." << std::endl;
}
// normal copy assignment operator taking lvalue
Sample& operator=(const Sample& s)
{
if(this != &s) {
delete [] ptr; // free current pointer
size = s.size;
if (size != 0) {
ptr = new int[s.size];
memcpy(ptr, s.ptr, s.size);
} else
ptr = nullptr;
}
cout << "Copy Assignment called on lvalue." << std::endl;
return *this;
}
// overloaded move assignment operator taking rvalue
Sample& operator=(Sample&& lhs)
{
if(this != &s) {
delete [] ptr; //don't let ptr be orphaned
ptr = lhs.ptr; //but now "steal" lhs, don't clone it.
size = lhs.size;
lhs.ptr = nullptr; // lhs's new "stolen" state
lhs.size = 0;
}
cout << "Move Assignment called on rvalue" << std::endl;
return *this;
}
//...snip
};
构造函数和赋值运算符已被采用右值引用的版本重载。右值引用允许函数在编译时(通过重载解析)在“我是在左值还是右值上被调用?”的条件下进行分支。 这使我们能够在上面创建更高效的构造函数和赋值运算符来移动资源而不是复制它们。
编译器在编译时自动分支(取决于它是为左值还是右值调用)选择是否应该调用移动构造函数或移动赋值运算符。
总结:右值引用允许移动语义(和完美转发,在下面的文章链接中讨论)。
一个易于理解的实用示例是类模板std::unique_ptr。由于 unique_ptr 维护其底层原始指针的独占所有权,因此无法复制 unique_ptr。这将违反他们的独占所有权不变式。所以他们没有复制构造函数。但他们确实有移动构造函数:
template<class T> class unique_ptr {
//...snip
unique_ptr(unique_ptr&& __u) noexcept; // move constructor
};
std::unique_ptr<int[] pt1{new int[10]};
std::unique_ptr<int[]> ptr2{ptr1};// compile error: no copy ctor.
// So we must first cast ptr1 to an rvalue
std::unique_ptr<int[]> ptr2{std::move(ptr1)};
std::unique_ptr<int[]> TakeOwnershipAndAlter(std::unique_ptr<int[]> param,\
int size)
{
for (auto i = 0; i < size; ++i) {
param[i] += 10;
}
return param; // implicitly calls unique_ptr(unique_ptr&&)
}
// Now use function
unique_ptr<int[]> ptr{new int[10]};
// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(\
static_cast<unique_ptr<int[]>&&>(ptr), 10);
cout << "output:\n";
for(auto i = 0; i< 10; ++i) {
cout << new_owner[i] << ", ";
}
output:
10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
static_cast<unique_ptr<int[]>&&>(ptr) 通常使用 std::move
// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(std::move(ptr),0);
Thomas Becker 的C++ Rvalue References Explained 是一篇很好的文章,解释了所有这些以及更多内容(例如右值如何实现完美转发以及这意味着什么),并提供了很多很好的示例。这篇文章很大程度上依赖于他的文章。
Stroutrup 等人的简短介绍是A Brief Introduction to Rvalue References。人