Copying objects is expensive. Whether it’s a large std::vector, a buffer, or a file handle — copying means duplicating memory, and that costs performance.
Move semantics, introduced in C++11, is a powerful solution that allows us to transfer ownership of resources rather than duplicating them. It enables efficient memory and resource management — especially for temporary objects.
What are Move Semantics?
Move semantics allow an object to steal the internal resources (e.g., dynamic memory, file handles) of another object rather than copying them.
It involves:
Move constructor
Move assignment operator
Both are designed to transfer data from one object to another, leaving the source in a valid but empty state.
Why Do We Need Move Semantics?
❌ The Problem with Copying
Suppose you have a class that holds a dynamically allocated array:
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
~Buffer() {
delete[] data;
}
Buffer(const Buffer& other); // Copy constructor
Buffer& operator=(const Buffer& other); // Copy assignment
};
Copying a Buffer involves deep copying the internal array — expensive for large data.
✅ Enter Move Semantics
With move semantics, you can write:
Buffer(Buffer&& other); // Move constructor
Buffer& operator=(Buffer&& other); // Move assignment
This allows you to transfer ownership of data from one object to another without copying the contents.
Copy vs Move: Key Differences
| Feature | Copy | Move |
|---|---|---|
| Purpose | Duplicate resource | Transfer resource ownership |
| Performance | Slow (deep copy) | Fast (pointer swap, no copy) |
| Used when | Lvalues (named variables) | Rvalues (temporaries, std::move) |
A Minimal Example
Step 1: A Class With Move Semantics
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
std::cout << "Constructed\n";
}
~Buffer() {
delete[] data;
std::cout << "Destroyed\n";
}
// Copy constructor
Buffer(const Buffer& other) : size(other.size) {
data = new int[size];
std::copy(other.data, other.data + size, data);
std::cout << "Copied\n";
}
// Move constructor
Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
std::cout << "Moved\n";
}
// Move assignment
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data; // clean up current resource
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
std::cout << "Move Assigned\n";
}
return *this;
}
};
Step 2: Usage Example
Buffer makeBuffer() {
Buffer temp(1000);
return temp; // Move constructor is called here
}
int main() {
Buffer b1 = makeBuffer(); // Moved from temp
Buffer b2 = std::move(b1); // Explicit move
}
When to Use std::move
Use std::move(obj) when you:
Want to explicitly transfer ownership
Know that
objwon’t be used again in its original form
Example:
Buffer a(1000);
Buffer b = std::move(a); // a is now in an empty/valid state
⚠️ After std::move(a), the original a should not be used (except maybe to destroy or reset).
STL & Move Semantics
The Standard Template Library (STL) uses move semantics heavily:
std::vectorwill move elements instead of copying them when resizingstd::unique_ptris non-copyable, but movableemplace_back()uses move semantics by default
This makes your code:
Faster
Safer
Cleaner
The Role of std::move
std::move is an utility function that facilitates the use of move semantics. It is defined in the <utility> header and is a key component in implementing efficient resource transfers. The primary purpose of std::move is to cast an object tot an rvalue reference, indicating that it can moved from.
Basic Syntax:
The syntax of std::move is simple:
#include <utility>
T&& std::move(T&& arg);
Here, T represents the type of the object, and arg is the object to be cast to an rvalue reference. The result of std::move is an rvalue reference of type T.
How std::move Works
When you apply std::move to an object, you are essentially telling the compiler that you are done with the object, and it can safely “move” the resources it owns. This is particularly useful in scenarios involving dynamic memory allocation or resource management.
Consider the following example:
#include <iostream>
#include <vector>
int main() {
std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> destination = std::move(source);
std::cout << "Source size: " << source.size() << std::endl; // Undefined behavior
std::cout << "Destination size: " << destination.size() << std::endl; // Outputs 5
return 0;
}
// Output
Source size: 0
Destination size: 5
In this example, std::move is used to transfer the contents of the source vector to the destination vector. After the move, source is left in a valid but unspecified state, and accessing its size is undefined behavior.
Leave a comment
Your email address will not be published. Required fields are marked *


