C++ has the concept of an rvalue, r as in right hand side, denotes a value, denotes something that can be on the right hand side of an assignment basically a temporary object.
For example take the expression:
Here A, B, C and D are variables and therefore l-values as they are normal in language objects that can be assigned to. The operation B + C creates a new temporary object, which is then used to multiply with D, which then generates a new object that is then assigned to A. These are r-values. They are temporary objects that are only created on the right hand side of the assignment to be intermediate steps in the computation.
It get's clear when rewriting this as a set of instructions:
Add(out rval1, B, C);
Mul(out rval2, rval1, D);
Assign(A, rval2);
In the C++ rvalues are generally speaking the result of constructors and basic operators. The C++ typesystem allows to manage references to rvalues with &&, so (MyType &&) represents a reference to an rvalue of MyType.
This allows you to overload operators that only work on rvalues, most notable the move constructor:
struct MyStruct {
MyStruct() {} // Default constructor
MyStruct &operator=(MyStruct &&fromStruct) {
std::cout << "Move temporary\n";
return *this;
}
MyStruct &operator=(MyStruct const &fromStruct) {
std::cout << "Copy from existing\n";
return *this;
}
};
int main() {
MyStruct m1, m2, m3;
m1 = MyStruct{}; // Move temporary
m2 = m1; // Copy from existing
}
This allows for quite a bit more control, because in the rvalue constructure (move temporary), you know that the object will afterwards be destroyed. So you do not need to do a copy. So take this simple list implementation:
struct MyList {
void **data; // Will be freed in destructor
size_t len;
MyList &operatator=(MyList &&fromList) { // RValue assignment: just take the data
free(this
->data
); // clear old data this->data = fromList.data; // Grab old data from assignment
this->len = fromList.len;
fromList.data = nullptr; // Because we "stole" their data, set it to nil so it doesn't try to free our list
}
MyList &operatator=(MyList &&fromList) { // Copy assignment: create copy of data
this
->data
= malloc(sizeof(void*) * fromList.
len); memcpy(this
->data
*, fromList.
data*, sizeof(void*)*fromList.
len); // Make copy of data this->len = fromList.len;
}
};
The interesting thing now is, that when the object is a temporary object, we will not perform a copy of the data, but simply grab that data, and replace it with null, so only one pointer must be copied. But when you try to assign something that is persistent and will be usable later on, you must make a your own copy.
This can be combined with the C++ function move, which turns any object into an rvalue:
MyList l1, l2;
l1 = l2; // Copies l2
l1 = std::move(l2); // moves the data from l2 into l1 without copying
But what is very important here is that after std::move, l2 is now not filled anymore, because the data has been extracted l2 is now empty and useless until it is initialized again.