rvalues & rvalue references
- In C++, a lvalue is something of which you can take an address, for example, a named variable. They can appear on the lefthand side of an assignment.
- A rvalue, is anything that is not an lvalue.
- A rvalue reference is a reference to an rvalue.
- A function can specify an rvalue reference parameter by using
&&
as part of the parameter specification, such astype&& name
.
// lvalue reference parameter
void handleMsg(std::string& msg)
{
cout << "handleMsg with lvalue reference: " << msg << endl;
}
// rvalue reference parameter
void handleMsg(std::string&& msg)
{
cout << "handleMsg with rvalue reference: " << msg << endl;
}
// Example
std::string a = "Hello ";
std::string b = "World";
handleMsg(a); // Calls handleMsg(string& value)
handleMsg(a + b) // Calls handleMsg(string&& value)
// If remove the handleMsg() accepting lvalue reference,
// calling handleMsg(b) will result in a compilation error,
// rvalue reference parameter will never bound to a lvalue.
// You can force to call rvalue reference version by using
// std::move(), which casts a lvalue into a rvalue
handleMsg(std::move(b)); // Calls handleMsg(string&& value)
A named variable is lvalue, so inside the handleMsg(string&& value)
function, the value
is a lvalue because it has a name,
If you want to forward the rvalue reference parameter to another function as an rvalue, you need to use std::move()
to cast the lvalue to rvalue.
// rvalue reference parameter
void handleMsg(std::string&& msg)
{
cout << "handleMsg with rvalue reference: " << msg << endl;
helper(std::move(msg)); // cast to rvalue
}
// rvalue reference parameter
void helper(std::string&& msg)
{
...
}
Implementing Move Semantics
To add move semantics to a class, you need to implement a move constructor and a move assignment operator, which should be marked with the noexcept
qualifier to tell the compiler that they don't throw any exceptions.
class Simple
{
public:
Simple(Simple&& src) noexcept; // move constructor
Simple& operator=(Simple&& rhs) noexcept; // move assign
};
Move constructors and move assignment operators can be explicitly deleted or defaulted.
The compiler automatically generates a default move constructor for a class if and only if the class has no user-declared copy constructor, copy assignment operator, move assignment operator, or destructor.
A default move assignment operator is generated for a class if and only if the class has no user-declared copy constructor, move constructor, copy assignment operator, or destructor.
Rule of Five1
If you have dynamically allocated memory in your class, then you typically should implement adestructor
,copy constructor
,move constructor
,copy assignment operator
, andmove assignment operator
.
Moving Object Data Members
class Simple
{
private:
Simple() = default;
public:
// move constructor
Simple(Simple&& src) noexcept
: Simple()
{
swap(*this, src);
}
// move assign
Simple& operator=(Simple&& rhs) noexcept
{
Simple temp(std::move(rhs));
swap(*this, temp);
return *this;
}
};
You only have to update your swap()
implementation to include the new data members.
Two different implementation of swap:
void swapCopy(T& a, T& b)
{
T temp(a);
a = b;
b = temp;
}
void swapMove(T& a, T& b)
{
T temp(std::move(a));
a = std::move(b);
b = std::move(temp);
}
Rule of zero2
you should design your classes in such a way that they do not require any of those five special member functions1.
In modern C++, adopt the rule of zero!