DEV Community

Cover image for Everything about std::move()
llzzz
llzzz

Posted on Edited on AI-assisted

Everything about std::move()

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);
    }
};
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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

}
Enter fullscreen mode Exit fullscreen mode

If you run the program, you will see the following get printed:

move ctor
move assignment
Enter fullscreen mode Exit fullscreen mode

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)