Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Casting to Rvalue References
Cast lvalues to rvalues to enable move semantics explicitly.
std::move
Once you start using move semantics regularly, you'll encounter cases where you want to invoke move semantics, but the objects you have are l-values, not r-values. Consider the following swap function:
#include <iostream>
#include <string>
template <typename T>
void swapCopy(T& first, T& second)
{
T temp{first}; // invokes copy constructor
first = second; // invokes copy assignment
second = temp; // invokes copy assignment
}
int main()
{
std::string name1{"Alice"};
std::string name2{"Bob"};
std::cout << "name1: " << name1 << '\n';
std::cout << "name2: " << name2 << '\n';
swapCopy(name1, name2);
std::cout << "name1: " << name1 << '\n';
std::cout << "name2: " << name2 << '\n';
return 0;
}
Given two objects of type T (in this case, std::string), this function swaps their values by making three copies. Consequently, this program prints:
name1: Alice
name2: Bob
name1: Bob
name2: Alice
As we demonstrated in previous lessons, making copies can be inefficient. And this version of swap makes 3 copies. That leads to excessive string creation and destruction, which is slow.
However, copying isn't necessary here. All we're trying to do is swap the values of first and second, which can be accomplished just as well using 3 moves instead! Switching from copy semantics to move semantics would make our code more performant.
But how? The problem is that parameters first and second are l-value references, not r-value references, so we don't have a way to invoke the move constructor and move assignment operator instead of copy constructor and copy assignment. By default, we get the copy constructor and copy assignment behaviors. What can we do?
std::move
In C++11, std::move is a standard library function that casts (using static_cast) its argument into an r-value reference, allowing move semantics to be invoked. Thus, we can use std::move to cast an l-value into a type that will prefer being moved over being copied. std::move is defined in the utility header.
Here's the same program, but with a swapMove() function using std::move to convert our l-values into r-values so we can invoke move semantics:
#include <iostream>
#include <string>
#include <utility> // for std::move
template <typename T>
void swapMove(T& first, T& second)
{
T temp{std::move(first)}; // invokes move constructor
first = std::move(second); // invokes move assignment
second = std::move(temp); // invokes move assignment
}
int main()
{
std::string name1{"Alice"};
std::string name2{"Bob"};
std::cout << "name1: " << name1 << '\n';
std::cout << "name2: " << name2 << '\n';
swapMove(name1, name2);
std::cout << "name1: " << name1 << '\n';
std::cout << "name2: " << name2 << '\n';
return 0;
}
This prints the same result:
name1: Alice
name2: Bob
name1: Bob
name2: Alice
But it's much more efficient! When temp is initialized, instead of making a copy of first, we use std::move to convert l-value variable first into an r-value. Since the parameter is an r-value, move semantics are invoked, and first is moved into temp.
With a couple more swaps, the value of first has been moved to second, and the value of second has been moved to first.
Another example
We can also use std::move when filling elements of a container, such as std::vector, with l-values.
In the following program, we first add an element to a vector using copy semantics. Then we add an element to the vector using move semantics.
#include <iostream>
#include <string>
#include <utility> // for std::move
#include <vector>
int main()
{
std::vector<std::string> names;
// We use std::string because it is movable (std::string_view is not)
std::string username{"Charlie"};
std::cout << "Copying username\n";
names.push_back(username); // calls l-value version of push_back, which copies username into the array element
std::cout << "username: " << username << '\n';
std::cout << "vector: " << names[0] << '\n';
std::cout << "\nMoving username\n";
names.push_back(std::move(username)); // calls r-value version of push_back, which moves username into the array element
std::cout << "username: " << username << '\n'; // The result of this is indeterminate
std::cout << "vector:" << names[0] << ' ' << names[1] << '\n';
return 0;
}
When tested, this program prints:
Copying username
username: Charlie
vector: Charlie
Moving username
username:
vector: Charlie Charlie
In the first case, we passed push_back() an l-value, so it used copy semantics to add an element to the vector. For this reason, the value in username is left unchanged.
In the second case, we passed push_back() an r-value (actually an l-value converted via std::move), so it used move semantics to add an element to the vector. This is more efficient, as the vector element can steal the string's value rather than copying it.
Moved-from objects will be in a valid, but possibly indeterminate state
When we move the value from a temporary object, it doesn't matter what value the moved-from object is left with, because the temporary object will be destroyed immediately anyway. But what about lvalue objects that we've used std::move() on? Because we can continue to access these objects after their values have been moved (e.g., in the example above, we print the value of username after it has been moved), it's useful to know what value they're left with.
There are two schools of thought here. One believes objects that have been moved from should be reset back to some default/zero state, where the object no longer owns a resource. We see an example above, where username has been cleared to the empty string.
The other school believes we should do whatever is most convenient, without constraining ourselves to having to clear the moved-from object if it's not convenient to do so.
What does the standard library do? About this, the C++ standard says, "Unless otherwise specified, moved-from objects [of types defined in the C++ standard library] shall be placed in a valid but unspecified state."
In our example above, when we printed the value of username after calling std::move on it, it printed an empty string. However, this is not required, and it could have printed any valid string, including an empty string, the original string, or any other valid string. Therefore, we should avoid using the value of a moved-from object, as the results will be implementation-specific.
In some cases, we want to reuse an object whose value has been moved (rather than allocating a new object). For example, in the implementation of swapMove() above, we first move the resource out of first, and then we move another resource into first. This is fine because we never use the value of first between the time where we move it out and the time where we give first a new determinate value.
With a moved-from object, it's safe to call any function that doesn't depend on the current value of the object. This means we can set or reset the value of the moved-from object (using operator=, or any kind of clear() or reset() member function). We can also test the state of the moved-from object (e.g., using empty() to see if the object has a value). However, we should avoid functions like operator[] or front() (which returns the first element in a container), because these functions depend on the container having elements, and a moved-from container may or may not have elements.
std::move() gives a hint to the compiler that the programmer doesn't need the value of an object anymore. Only use std::move() on persistent objects whose value you want to move, and do not make any assumptions about the value of the object beyond that point. It is okay to give a moved-from object a new value (e.g., using operator=) after the current value has been moved.
Where else is std::move useful?
std::move can also be useful when sorting an array of elements. Many sorting algorithms (such as selection sort and bubble sort) work by swapping pairs of elements. In previous lessons, we've had to resort to copy-semantics to do the swapping. Now we can use move semantics, which is more efficient.
It can also be useful when we want to move the contents managed by one smart pointer to another.
Advanced note: There is a useful variant of std::move() called std::move_if_noexcept() that returns a movable r-value if the object has a noexcept move constructor, otherwise it returns a copyable l-value.
Summary
The problem: L-value references invoke copy semantics by default. When you want to move resources from an l-value (rather than copy), you need a way to indicate this intent.
std::move() functionality: Casts its argument into an r-value reference, allowing move semantics to be invoked on l-values. Defined in the
Efficient swapping: Using std::move(), a swap function can use three moves instead of three copies, significantly improving performance for types with expensive copy operations.
Container operations: std::move() allows efficient transfer of elements into containers like std::vector when you no longer need the source object.
Moved-from object state: The C++ standard specifies that moved-from objects are placed in a "valid but unspecified state." The object is safe to destroy or reassign, but its value is implementation-defined and should not be used.
Safe operations on moved-from objects: After moving, you can assign new values to the object, call clear() or reset(), or test state with empty(). Avoid operations that depend on the current value like operator[] or front().
Sorting applications: std::move() is useful in sorting algorithms that swap elements, allowing moves instead of copies for better performance.
Smart pointer transfers: std::move() enables transferring ownership between smart pointers efficiently.
Use std::move() only on objects whose value you no longer need. Do not use the value of a moved-from object. You may assign new values to moved-from objects or test their state with functions like empty().
Casting to Rvalue References - Quiz
Test your understanding of the lesson.
Practice Exercises
Custom Swap with std::move
Implement an efficient swap function using std::move that works with a resource-managing class. Demonstrate the performance difference between copy-based and move-based swapping.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!