Demystifying std::move in C++

It doesn’t move the object, but gets it prepared for move

Dagang Wei
3 min read10 hours ago

This blog post is part of the series Modern C++.

The Art of Moving

Modern C++ equips us with powerful tools for resource management, and std::move is a shining example. While seemingly simple, std::move is the gateway to move semantics, a technique that can significantly enhance your code's performance.

In the world of C++, copying objects involves duplicating their entire contents. This can be computationally costly, especially when handling large objects or complex structures. But what if we’re about to discard the original object anyway?

That’s where the concept of “moving” comes to the rescue. Instead of creating a costly copy, we transfer ownership of an object’s resources to a new object. The original object is left in an empty state, effectively saving us the overhead of unnecessary duplication.

The Role of std::move

Contrary to what the name might suggest, std::move doesn't perform the move operation itself. It's a cast – a mechanism that transforms an lvalue (typically an object you can assign to) into an rvalue (often a temporary object or one intended to be discarded). This transformation signals to the compiler that it's okay to "steal" the resources from the original object to optimize the code.

Fundamentally, std::move expresses your explicit intent to move an object rather than copy it. This allows the compiler to activate move constructors and move assignment operators if they're available for the object's type.

Crafting Your Own std::move

Let’s create our simplified implementations of std::move to illustrate how it works:

// Simplified std::remove_reference implementation
template <typename T>
struct remove_reference {
using type = T;
};

// Specialization for lvalue references (e.g., int&)
template <typename T>
struct remove_reference<T&> {
using type = T;
};

// Specialization for rvalue references (e.g., int&&)
template <typename T>
struct remove_reference<T&&> {
using type = T;
};


// Simplified std::move implementation
template <typename T>
typename remove_reference<T>::type&& my_move(T&& arg) {
// This function casts its argument into an rvalue reference,
// signaling that the object can be moved from.
return static_cast<typename remove_reference<T>::type&&>(arg);
}

Here is the test code:

class A {
public:
A() = default; // Default constructor

A(const A& other) { // Copy constructor (for comparison)
std::cout << "Copy constructor called\n";
data = other.data;
}

A(A&& other) noexcept { // Move constructor
std::cout << "Move constructor called\n";
data = std::move(other.data); // Use std::move or our simplified move
other.data = ""; // Reset the source object
}

std::string data;
};

int main() {
A a1;
a1.data = "Hello";
std::cout << "a1 before move: " << a1.data << std::endl;
A a2 = my_move(a1); // Use our move
std::cout << "a1 after move: " << a1.data << std::endl;
std::cout << "a2: " << a2.data << std::endl;

return 0;
}

Output:

a1 before move: Hello
Move constructor called
a1 after move:
a2: Hello

In this example, our custom my_move function successfully triggers the move constructor, just like std::move would.

When to Employ std::move

Consider using std::move in these scenarios:

  • Returning Objects by Value: When a function returns an object by value, moving it can be significantly more efficient than copying.
  • Passing Objects to Constructors/Functions: If you’re providing an object to a constructor or function and the original object is no longer needed, moving it conserves resources.

Takeaway

Mastering move semantics and wielding std::move effectively can elevate your C++ code to new levels of performance. By strategically replacing unnecessary copies with move operations, you'll optimize your applications for speed and efficiency.

--

--