Professional C__ - Marc Gregoire [138]
int a = 2, b = 3;
int& j = a + b; // Invalid: reference to a temporary
Using rvalue references, the following is perfectly legal:
int&& i = 2;
int a = 2, b = 3;
int&& j = a + b;
Standalone rvalue references as in the preceding example are rarely used as such. However, they can be useful as parameters to functions or methods as the following example demonstrates. The code first defines two incr() functions, one accepting an lvalue reference and one accepting an rvalue reference.
// Increment value using lvalue reference parameter.
void incr(int& value)
{
cout << "increment with lvalue reference" << endl;
++value;
}
// Increment value using rvalue reference parameter.
void incr(int&& value)
{
cout << "increment with rvalue reference" << endl;
++value;
}
Code snippet from References\RvalueReferences.cpp
You can call the incr() function with a named variable as argument as follows. Because a is a named variable, the incr() function accepting an lvalue reference is called. After the call to incr(), the value of a will be 11.
int a = 10, b = 20;
incr(a); // Will call incr(int& value)
Code snippet from References\RvalueReferences.cpp
You can also call the incr() function with an expression as argument as follows. The incr() function accepting an lvalue reference cannot be used, because the expression a + b results in a temporary, which is not an lvalue. In this case the rvalue reference version is called. Since the argument is a temporary, the incremented value is actually lost after the call to incr().
incr(a + b); // Will call incr(int&& value)
Code snippet from References\RvalueReferences.cpp
A literal can also be used as argument to the incr() call. This will also trigger a call to the rvalue reference version because a literal cannot be an lvalue.
incr(3); // Will call incr(int&& value)
Code snippet from References\RvalueReferences.cpp
If you remove the definition accepting an lvalue reference, incr(int& value), calling incr() with a named variable like incr(b) will result in a compiler error, because the C++ standard says that an rvalue reference parameter (int&& value) will never be bound to an lvalue (b). You can force the compiler to call the rvalue reference version of incr() by using std::move() which converts an lvalue into an rvalue as follows. After the incr() call, the value of b will be 21.
incr(std::move(b)); // Will call incr(int&& value)
Code snippet from References\RvalueReferences.cpp
Move Semantics
C++11 adds the concept of move semantics to objects by implementing a move constructor and a move assignment operator. These will be used by the compiler on places where the second object is a temporary object that will be destroyed after the copy or assignment. Both the move constructor and the move assignment operator copy the member variables from the source object to the new object and then reset the variables of the source object to null values. By doing this, they are actually moving ownership of the memory from one object to another object. They basically do a shallow copy of the member variables and switch ownership of allocated memory to prevent dangling pointers or memory leaks.
Move semantics is implemented by using rvalue references. To add move semantics to a class, a move constructor and a move assignment operator need to be implemented. Following is the Spreadsheet class definition from Chapter 7, which now includes a move constructor and a move assignment operator.
class Spreadsheet
{
public:
Spreadsheet(Spreadsheet&& src); // Move constructor
Spreadsheet& operator=(Spreadsheet&& rhs); // Move assignment
// Remaining code omitted for brevity
};
Code snippet from SpreadsheetMoveSemantics\Spreadsheet.h
The implementation is as follows.
// Move constructor
Spreadsheet::Spreadsheet(Spreadsheet&& src)
{
// Shallow copy of data
mWidth = src.mWidth;
mHeight = src.mHeight;
mCells = src.mCells;
// Reset the source object
src.mWidth = 0;
src.mHeight = 0;
src.mCells = nullptr;
}
// Move assignment operator
Spreadsheet& Spreadsheet::operator=(Spreadsheet&&