Introduction
When someone tell you they use modern C++, std::move() should be the first thing that come to your mind as it is so important that almost every codebase after C++11 use it. But do you actually understand how it works?
std::move is one of the most misunderstood tools in modern C++. The most important thing to know upfront: std::move does not actually move anything. It is simply a cast that converts an expression into an rvalue, signaling to the compiler that the object's resources may be "stolen" by a move constructor or move assignment operator.
How it is implemented?
std::move() defined in the <utility> header, its implementation is roughly equivalent to a static_cast to an rvalue reference.
namespace std {
template <typename T>
constexpr decltype(auto) move(T&& t) noexcept {
return static_cast<std::remove_reference_t<T>&&>(t);
}
};
So based on the implementation, it doesn't really move anything, you are basically telling the compiler that you no longer need the current value; just treat it as a temporary so that a move operation can be selected via overload resolution. The actual moving happen if the target type has a move constructor/move assignment defined.
When do you need to use move?
The primary legitimate use case is transferring ownership of resources from a named object that will not be used again.
Moving into the container
std::string str = "Hello, World!";
std::vector<std::string> v;
v.push_back(std::move(str)); // str is now valid but unspecified (likely empty)
Here, std::move enables the push_back(T&&) overload, avoiding a deep copy of the string's buffer (but you may want to use emplace_back if you can directly construct the string in the container).
Implement move semantics in your class
Inside a move constructor or move assignment operator, parameters (like other) are lvalues even though their type is T&&. You must use std::move on their members to propagate the move. Here is an example:
#include <print>
struct A {
A() = default;
A(A&&) { std::println("move ctor"); }
A& operator=(A&&) {
std::println("move assignment");
return *this;
}
};
int main() {
A a{}; // default init
A b{std::move(a)}; // move ctor
a = std::move(b); // move assignment
}
If you run the program, you will see the following get printed:
move ctor
move assignment
However, there are something we must consider before using it:
Misconceptions and Pitfalls
"It Doesn't Guarantee a Move"
Using std::move does not guarantee that a move will occur. If the type does not have a move constructor (or if the move constructor is not noexcept in certain contexts like vector reallocation), the compiler will fall back to copying. A common trap is calling std::move on a const object; since a move constructor typically requires a non-const rvalue reference, the operation will silently degrade to a copy.
"The Moved-From Object is Unusable"
The C++ standard guarantees that a moved-from standard library object is in a "valid but unspecified state". This means you can safely destroy it or assign a new value to it. However, you cannot rely on its contents (e.g., a moved-from std::string is often empty, but this is not guaranteed).
"Don't Use It on Return Values"
Writing return std::move(local_variable); is usually a pessimization. The compiler is already allowed (and often required) to perform Return Value Optimization (RVO) or implicitly treat the local variable as an rvalue. Adding std::move can actually prevent copy elision, forcing an unnecessary move (or even a copy).
"Don't Use It on Universal References"
In a template function with a forwarding reference T&&, using std::move(t) is dangerous. It will unconditionally cast to an rvalue, even if the caller passed an lvalue, potentially "stealing" resources from an object the caller still intends to use. Use std::forward<T>(t) instead.
Then, what is std::forward<T>(t), you might ask, this will be the discussion of another article!
So std::move is a tool for expressing intent. You are telling the compiler and other devs that you want to transfer the ownership of the object. Usually on the object that is inside a function and you are certain that it will not be access again. But remember that never use it on a const object as it only make your code messier and silently failed!
Top comments (0)