C++ Move Semantics: When and How to Transfer Resources Without Copying
Move semantics transfer ownership of resources from temporary objects using rvalue references (T&&) without copying. std::vector<std::string> v2 = std::move(v1) demonstrates this transfer of internal buffer.
20 Dec 2025, 14:41 UTC

The Core Problem: Expensive Copies in C++
When you write std::vector v2 = v1, the compiler creates a deep copy of every element. For large vectors, this means allocating new memory and copying each string—a costly operation. Move semantics solve this by transferring ownership of the internal buffer instead.
How std::move Works: It's Just a Cast
std::move never moves anything. It casts an lvalue to an rvalue reference (T&&), signaling that the source object can be safely pilfered. The actual move happens in the move constructor or assignment operator.
Worked Example: Moving a Vector
#include <vector>
#include <string>
int main() {
// Create a large vector with 1 million strings
std::vector<std::string> v1(1000000, "hello world");
// Move constructor: transfers v1's buffer to v2
std::vector<std::string> v2 = std::move(v1);
// v1 is now in a valid but unspecified state
// Do NOT access v1's elements here
return 0;
}
In this example, v2 takes ownership of v1's memory buffer. No strings are copied. After the move, v1 is empty but can be safely destroyed or reassigned.
The Rule of Five: Your Move Checklist
If your class manages resources (raw pointers, file handles, etc.), you need five special member functions:
- Destructor: Releases resources
- Copy constructor: Deep copies resources
- Copy assignment operator: Deep copies, handles self-assignment
- Move constructor: Transfers ownership, leaves source valid
- Move assignment operator: Transfers ownership, handles self-move
Custom Move Implementation Example
class Buffer {
private:
char* data_;
size_t size_;
public:
// Move constructor
Buffer(Buffer&& other) noexcept :
data_(other.data_), size_(other.size_) {
other.data_ = nullptr;
other.size_ = 0;
}
// Move assignment operator
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) { // Self-move check
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
// Rule of Five: also need destructor, copy constructor, copy assignment
~Buffer() { delete[] data_; }
Buffer(const Buffer& other); // Deep copy
Buffer& operator=(const Buffer& other); // Deep copy
};
Common Mistakes and Their Costs
Mistake 1: Using Moved-From Objects
After std::move(v1), accessing v1's elements is undefined behavior:
// WRONG: undefined behavior
std::vector<int> v1(10);
std::vector<int> v2 = std::move(v1);
int x = v1[0]; // Undefined!
// CORRECT: only destroy or reassign
v1 = std::vector<int>(5); // Safe reassignment
Mistake 2: Overusing std::move and Preventing Copy Elision
Copying from a prvalue (pure rvalue) is often better than moving:
// WRONG: prevents copy elision
std::vector<int> create_vector() {
return std::move(std::vector<int>(100)); // Forces move
}
// CORRECT: compiler may elide copy
std::vector<int> create_vector() {
return std::vector<int>(100); // Copy elision applies
}
Mistake 3: Forgetting Self-Move Assignment
If operator=(T&&) doesn't check for self-move, it can delete its own data:
// WRONG: self-move destroys data
Buffer& operator=(Buffer&& other) noexcept {
delete[] data_; // Oops! Deletes our data if self-move
data_ = other.data_;
// ...
}
Performance Verification
Check Noexcept Move Constructors
std::vector uses move operations for optimization only if they're noexcept:
static_assert(std::is_nothrow_move_constructible<MyType>::value,
"Move constructor must be noexcept for vector optimizations");
Compiler Warnings to Enable
g++ -std=c++17 -Wdeprecated-copy -Werror=return-type
-Wdeprecated-copy: Warns when copy constructor is implicitly deleted due to move operations-Werror=return-type: Catches missing return statements in move assignment
Limitations: Not All Types Benefit
Move semantics help most with resource-heavy types (vectors, strings, smart pointers). For primitive types like int or double, move is typically no different from copy. Use sizeof(T) and profiling to verify benefit.
Key Takeaway
Move semantics transfer ownership without copying, but only when used correctly. Always ensure moved-from objects are only destroyed or reassigned, implement the Rule of Five for resource-owning types, and verify noexcept correctness for standard library compatibility.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.