Move Semantics and Rvalue References in C++
Move semantics is a C++11 feature that allows the efficient transfer of resources from one object to another without copying. This dramatically improves performance when working with large objects like strings, vectors, and custom classes that manage heap memory.
Lvalue vs Rvalue
To understand move semantics, two fundamental concepts must be clear:
- Lvalue — An object with a persistent identity and an address in memory. Can appear on the left side of
=. Example: a variableint x = 5—xis an lvalue. - Rvalue — A temporary value with no persistent address. Typically on the right side of
=. Example: the literal5, or the result ofa + b.
int x = 10; // x is lvalue; 10 is rvalue
int y = x + 5; // x+5 is a temporary rvalue
Rvalue References — &&
C++11 introduced rvalue references (T&&) to detect and bind to temporary values, enabling efficient resource transfer.
int&& rref = 42; // rvalue reference to temporary
string&& sr = "Hello"; // rvalue reference to temporary string
The Copy Problem
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v1 = {1, 2, 3, 4, 5};
vector<int> v2 = v1; // COPY — expensive for large vectors
cout << "v1 size: " << v1.size() << endl; // 5
cout << "v2 size: " << v2.size() << endl; // 5
return 0;
}
Copying a large vector duplicates all its data in memory. When v1 is no longer needed, this wasted effort. Move semantics solves this.
Moving Resources with std::move
vector<int> v1 = {1, 2, 3, 4, 5};
vector<int> v2 = move(v1); // MOVE — no copy, just transfer ownership
cout << "v1 size: " << v1.size() << endl; // 0 — v1 is empty now
cout << "v2 size: " << v2.size() << endl; // 5 — v2 has the data
After move(), v1 is in a valid but unspecified (usually empty) state. The data was transferred, not duplicated.
Move Constructor and Move Assignment Operator
For custom classes, a move constructor and move assignment operator must be defined to enable efficient moving:
#include <iostream>
using namespace std;
class Buffer {
public:
int* data;
int size;
Buffer(int n) : size(n), data(new int[n]) {
cout << "Constructed (size=" << n << ")
";
}
// Copy constructor — expensive
Buffer(const Buffer& other) : size(other.size), data(new int[other.size]) {
copy(other.data, other.data + size, data);
cout << "Copied
";
}
// Move constructor — cheap
Buffer(Buffer&& other) noexcept : size(other.size), data(other.data) {
other.data = nullptr;
other.size = 0;
cout << "Moved
";
}
~Buffer() { delete[] data; }
};
int main() {
Buffer b1(100);
Buffer b2 = move(b1); // calls move constructor, not copy
return 0;
}
Output:
Constructed (size=100)
MovedMove Assignment Operator
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data; // free existing resource
data = other.data; // steal other's resource
size = other.size;
other.data = nullptr; // leave other in valid state
other.size = 0;
}
return *this;
}
Perfect Forwarding with std::forward
In template code, std::forward preserves the value category (lvalue/rvalue) of arguments when passing them to another function:
template <typename T>
void wrapper(T&& arg) {
process(forward<T>(arg)); // preserves lvalue/rvalue nature
}
When is Move Used Automatically?
- Returning a local variable from a function (NRVO / implicit move)
- Passing a temporary to a function expecting an rvalue reference
- Explicitly calling
std::move()
Rule of Five
If a class manages resources (like heap memory), it should define all five special functions:
- Destructor
- Copy constructor
- Copy assignment operator
- Move constructor
- Move assignment operator
Key Takeaways
- Move semantics transfer ownership of resources instead of copying them — much faster.
- Rvalue references (
T&&) bind to temporary objects. std::move()explicitly converts an lvalue into an rvalue reference to enable moving.- Custom classes with heap resources should implement a move constructor and move assignment operator.
- After moving, the source object is left in a valid but empty state.
