CS106L(14): Lovebird & Move Semantics
Lovebird is definitely cute, but a bit noisy:
They wanna get out of the cage every day:
Let’s learn the Move Semantics to help them.
Core Idea
Move Semantics: get existing resource without wasting new memory. Make C++ more efficient.
- lvalues vs. rvalues
- Move implementation
- Forcing a move to occur: std::move()
- swap and insert
Lecture 14: Move Semantics - Note
View Lecture Note
lvalues vs. rvalues
Use move constructor on the first line becuase it was temporary.
A move constructor of class T is a non-template constructor whose first parameter is T&&
, const T&&
, volatile T&&
, or const volatile T&&
, and either there are no other parameters, or the rest of the parameters all have default values.
int main() {
vector<string> words1 = findAllWords(12345); // move constructor
vector<string> words2 = words1; // copy assignment
}
- L-values and r-values generalize the idea of “temporariness.”
- An r-value is “temporary,” and an l-value is not.
r-value: tempoRaRy, or Rubbish value, so can be thrown away, l-value: long-lasting value, so can buy house in the city, so it has
mailing
address.
-
Official definition: a l-value has an address (can do &), and a r-value does not.
-
An l-value can appear
left
orright
of =. -
An r-value can only appear
right
of =.
Which of these are r-values?
int val = 2; // r-value
int* ptr = 0x02248837; // r-value
vector<int> v1{1, 2, 3}; // r-value
auto v4 = v1 + v2; // r-value
auto v5 = v1 += v4; // l-value
size_t size = v.size(); // r-value
val = static_cast<int>(size); // r-value
v1[1] = 4*i; // r-value
ptr = &val; // r-value
v1[2] = *ptr; // l-value
- A l-value’s lifetime is until end of scope.
- A r-value’s lifetime is until end of line.
- unless it is artificially extended.
r-value Reference
r-value cannot be referenced using &
Error! Cannot pass a literal value by reference! Will have Compiler Error.
int main() {
change(7);
}
void change(int& a) {...} // this doesn’t work
Move implementation
Move Constructor (rvalue&& reference)
Brutally rob other’s resources, because it is temporary and will disappear when the function exits.
Overloading between & and && versions of the same function allows us to disambiguate between copy and move.
Example:
vector<T>(vector<T>&& other): // Move Constructor
_size(other._size),
_capacity(other._capacity) {
// steal the other array 🕵
_elems = other._elems; // an l-value, performs a copy, to fix this, will introduce std::move
other._elems = nullptr;
other._size = 0;
}
Forcing a move to occur
Move Constructor:
vector<T>(vector<T>&& other):
_size(std::move(other._size)),
_capacity(std::move(other._capacity)) {
// steal the other array 🕵
_elems = std::move(other._elems); // std::move is a cast to a rvalue&&(equivalent to std::static_cast<T&&>)
other._elems = nullptr;
other._size = 0;
}
vector<T>& v = {1, 2, 3};
vector<T>& v2;
v2 = std::move(v); // std::move is a cast to a rvalue && (equivalent to std::static_cast<T&&>)
Takeaways
- Use a
constructor
taking a rvalue for move constructor - Use
operator=
taking a rvalue for move assignment - Use
std::move
to make sure other object’s values are treated as rvalues (and so moved)- Call
std::move
to force anything to become a rvalue (and get its data taken!)
- Call
std::move
does not move anything
swap and insert
Vector Example
template <typename T>
void vector<T>::push_back(const T& element) {
elems[_size++] = element; // equals → copy
}
template <typename T>
void vector<T>::push_back(T&& element) {
elems[_size++] = std::move(element); // move!
}
std:swap
template <typename T>
void swap(T& a, T& b) noexcept {
T c(std::move(a)); // move constructor
a = std::move(b); // move assignment
b = std::move(c); // move assignment
}
Haha, congrats, the 14th lecture finished! Seems CS106L is almost over, I will jump to another course CSE333 soon.