C++ Basics [03]: Rvalue and Rvalue References
Published:
Rvalue references is introduced in C++ 11 to support move functions, which allows programmers to avoid logically unnecessary copying and to provide perfect forwarding functions. They are primarily meant to aid in the design of higer performance and more robust libraries.
Rvalue
In C, the definition of Rvalue
is simple: lvalues
could stand on the left-hand side of an assignment whereas rvalues
could not.
In C++, however, the definition is less simple. Roughly speaking, when we use an object as an rvalue
, we use the object’s value (its contents). When we use an object as an lvalue
, we use the object’s identity (its location in memory). To be more specific, an lvalue
is an expression that refers to a memory location and allows us to take the address of that memory location via the &
operator. Others are rvalues
.
Lvalue Examples.
int i = 1;
int* ptr1 = &i; // i is an lvalue
int& foo();
foo() = 2;
int* ptr2 = &foo(); // foo() is an lvalue
In the above example, suppose foo()
is the following, then i
would become 2
after running foo() = 2
.
int& foo(){
return i;
}
Rvalue Examples.
int j = 0;
int* ptr;
int foobar();
j = foobar(); // foobar() is an rvalue
j = 1; // 1 is an rvalue
In the above example, suppose foobar()
is the following, then an error will be raised after running ptr = &foobar()
, as address-of operator &
requires an value operand.
int foovar(){
return i;
}
Rvalue References
An rvalue reference is obtained by using &&
rather than &
. Like any reference, an rvalue reference is just another name for an object. What is different is that an rvalue reference is a reference that must be bound to an rvalue
.
int i = 2;
int &r = i; // ok: r refers to i
int &&rr = i; // error: cannot bind an rvalue reference to an lvalue
int &r2 = i * 2; // error: i * 2 is an rvalue
const int &r3 = i * 2; // ok: we can bind a reference to const to an rvalue
int &&rr2 = i * 2; // ok: bind rr2 to the result of the multiplication
Functions that return lvalue references, along with the assignment, subscript, dereference, and prefix increment/decrement operators, are all examples of expressions that return lvalues. We can bind an
lvalue reference
to the result of any of these expressions.
Functions that return a nonreference type, along with the arithmetic, relational, bitwise, and postfix increment/decrement operators, all yield rvalues. We cannot bind an lvalue reference to these expressions, but we can bind either an
lvalue reference to const
or anrvalue reference
to such expressions.
Rvalues Are Ephemeral
It should be clear that lvalues and rvalues differ from each other in an important manner: Lvalues have persistent state, whereas rvalues are either literals or temporary objects created in the course of evaluating expressions. Because rvalue references can only be bound to temporaries, we know that
- The referred-to object is about to be destroyed
- There can be no other users of that object
These facts together mean that code that uses an rvalue reference
is free to take over resources from the object to which the reference refers.
Variables Are Lvalues
It should also be noted that a variable is an lvalue; we cannot directly bind an rvalue reference to a variable even if that variable was defined as an rvalue reference type.
int i = 1;
int &r = i; // ok: r refers to i
int &&rr1 = i*2; // ok: rr refres to i*2
int &&rr2 = rr1; // error: rr1 is an lvalue
int *ptr = &r;
cout << *ptr; // print out 1
ptr = &rr;
cout << *ptr; // print out 2
Move Operations
Rvalue reference is introduced to support move
operations. Consider:
template <class T> swap(T& a, T& b){
T tmp(a); // now we have two copies of a
a = b; // now we have two copies of b
b = tmp; // now we have two copies of tmp
}
But, we didn’t want to have any copies of a
or b
, we just wanted to swap them. Let’s try again:
template <class T> swap(T& a, T& b)
{
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
This move()
gives its target the value of its argument, but is not obliged to preserve the value of its source. This avoid having to copy all the elements. In other words, move is a potentially destructive read.
The move function really does very little work. All move does is accept either an lvalue
or rvalue
argument, and return it as an rvalue
without triggering a copy construction:
template <class T>
typename remove_reference<T>::type&& move(T&& a){ return a; }
Template will be summarized in the later posts.
Comments