📘 C++ Move Semantics & std::move() Tutorial
C++ std::move() is used to transfer the ownership of a variable/object.
🔹 What is Move Semantics?
In C++, move semantics optimize performance by transferring ownership of resources (like memory or file handles) instead of copying them.
Move semantics were introduced in C++11, and they allow:
- Faster transfers of large or expensive-to-copy objects
- More efficient use of temporary values
🔹 What is std::move()?
std::move(x)
does not move anything — it simply casts x
to an rvalue reference (i.e., T&&
), telling the compiler:
“You can treat this object as a temporary and move from it.”
To actually perform a move, your type must implement a move constructor or move assignment operator.
✅ When Should You Use std::move()?
Use it when:
- You want to transfer ownership of resources.
- You’re working with expensive-to-copy objects (e.g.,
std::string
,std::vector
,unique_ptr
). - You’re writing functions that take by value and want to move into members.
🔍 Basic Example with std::string
1 2 3 4 5 6 7 8 9 | #include <iostream> #include <string> #include <utility> int main() { std::string a = "hello"; std::string b = std::move(a); std::cout << "b: " << b << std::endl; std::cout << "a: " << a << std::endl; } |
#include <iostream> #include <string> #include <utility> int main() { std::string a = "hello"; std::string b = std::move(a); std::cout << "b: " << b << std::endl; std::cout << "a: " << a << std::endl; }
🔍 Moving a std::vector
1 2 3 | std::vector<int> original = {1, 2, 3}; std::vector<int> moved_to = std::move(original); // original is now empty (but still valid) |
std::vector<int> original = {1, 2, 3}; std::vector<int> moved_to = std::move(original); // original is now empty (but still valid)
⚠️ What Happens After Moving?
After moving:
- The moved-from object is still valid.
- But its content is unspecified — you can only destroy or assign to it.
Here is an example that shows you should not read the variable after it has been moved.
1 2 3 | std::string x = "abc"; std::string y = std::move(x); // x is now in a valid but unspecified state — don't read it! |
std::string x = "abc"; std::string y = std::move(x); // x is now in a valid but unspecified state — don't read it!
🧠 std::move() on Built-in Types
1 2 | int x = 42; int y = std::move(x); // just a copy, because int has no move semantics |
int x = 42; int y = std::move(x); // just a copy, because int has no move semantics
But it’s pointless, because primitive types like int
don’t have move constructors.
🛠️ Implementing Move in Custom Types
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class MyBuffer { int* data; size_t size; public: MyBuffer(size_t s) : size(s), data(new int[s]) {} // Move constructor MyBuffer(MyBuffer&& other) noexcept : data(other.data), size(other.size) { other.data = nullptr; other.size = 0; } // Move assignment MyBuffer& operator=(MyBuffer&& other) noexcept { if (this != &other) { delete[] data; data = other.data; size = other.size; other.data = nullptr; other.size = 0; } return *this; } ~MyBuffer() { delete[] data; } }; |
class MyBuffer { int* data; size_t size; public: MyBuffer(size_t s) : size(s), data(new int[s]) {} // Move constructor MyBuffer(MyBuffer&& other) noexcept : data(other.data), size(other.size) { other.data = nullptr; other.size = 0; } // Move assignment MyBuffer& operator=(MyBuffer&& other) noexcept { if (this != &other) { delete[] data; data = other.data; size = other.size; other.data = nullptr; other.size = 0; } return *this; } ~MyBuffer() { delete[] data; } };
Usage:
1 2 | MyBuffer a(1000); MyBuffer b = std::move(a); // Moves a into b |
MyBuffer a(1000); MyBuffer b = std::move(a); // Moves a into b
📦 std::move() with Smart Pointers
We can transfer ownership of a unique ptr or a shared ptr.
1 2 3 4 | #include <memory> std::unique_ptr<int> p1 = std::make_unique<int>(10); std::unique_ptr<int> p2 = std::move(p1); // p1 is now null |
#include <memory> std::unique_ptr<int> p1 = std::make_unique<int>(10); std::unique_ptr<int> p2 = std::move(p1); // p1 is now null
🔁 std::shared_ptr Ownership Transfer
When you “transfer ownership” of a shared_ptr
, you are:
- Moving the control block (which tracks reference count) from one
shared_ptr
to another. - The source
shared_ptr
becomes empty (use_count() == 0
). - The reference count remains unchanged overall (still 1, unless there are other shared owners).
✅ Example: Transferring ownership via std::move()
1 2 3 4 5 6 7 8 9 | #include <iostream> #include <memory> int main() { std::shared_ptr<int> p1 = std::make_shared<int>(42); std::cout << "p1 use_count: " << p1.use_count() << std::endl; // 1 std::shared_ptr<int> p2 = std::move(p1); // transfer ownership std::cout << "p1 is " << (p1 ? "not null" : "null") << std::endl; // null std::cout << "p2 use_count: " << p2.use_count() << std::endl; // 1 } |
#include <iostream> #include <memory> int main() { std::shared_ptr<int> p1 = std::make_shared<int>(42); std::cout << "p1 use_count: " << p1.use_count() << std::endl; // 1 std::shared_ptr<int> p2 = std::move(p1); // transfer ownership std::cout << "p1 is " << (p1 ? "not null" : "null") << std::endl; // null std::cout << "p2 use_count: " << p2.use_count() << std::endl; // 1 }
🔍 Important Distinction: shared_ptr vs unique_ptr
Pointer Type | Transfer Mechanism | Copies Allowed | Main Use Case |
---|---|---|---|
std::unique_ptr |
std::move() only |
❌ No copies | Sole ownership of resource |
std::shared_ptr |
std::move() or copy |
✅ Yes | Shared ownership with ref-count |
⚠️ Notes
- You can move a
shared_ptr
to transfer ownership (leaving the old one empty). - You can copy a
shared_ptr
to share ownership (both remain valid and share control block). - Never use
std::move()
unless you want the sourceshared_ptr
to become null.
🔄 Common Patterns
Moving from function return:
1 2 3 4 | std::string get_name() { std::string name = "Alice"; return std::move(name); } |
std::string get_name() { std::string name = "Alice"; return std::move(name); }
Use std::move()
here only if you want to force the move (e.g., when returning a function parameter).
🚫 When NOT to Use std::move()
1. ❌ Don’t move from something you still need:
1 2 3 | std::string s = "test"; std::string t = std::move(s); std::cout << s; // undefined contents |
std::string s = "test"; std::string t = std::move(s); std::cout << s; // undefined contents
2. ❌ Don’t use std::move() with const objects:
Const objects can’t be moved.
1 2 | const std::string s = "hi"; std::string t = std::move(s); // copies, not moves, because move constructor requires non-const |
const std::string s = "hi"; std::string t = std::move(s); // copies, not moves, because move constructor requires non-const
🧪 Summary Cheat Sheet
Use Case | Use std::move()? | Why? |
---|---|---|
Moving large containers/strings | ✅ Yes | Efficient memory/resource transfer |
Moving smart pointers | ✅ Yes | Transfers ownership |
Built-in types (e.g., int, bool) | 🚫 No | No move semantics — just a copy |
Const objects | 🚫 No | Move constructor not callable on const |
Temporaries | 🚫 Often no need | Already rvalues |
✅ Final Tip
If you’re unsure whether to use std::move()
, ask:
“Do I no longer need this variable and want to give it away?”
If yes → use std::move()
.
C/C++ Programming
- Tutorial on C++ std::move (Transfer Ownership)
- const vs constexpr in C++
- Tutorial on C++ Ranges
- Tutorial on C++ Smart Pointers
- Tutorial on C++ Future, Async and Promise
- The Memory Manager in C/C++: Heap vs Stack
- The String Memory Comparision Function memcmp() in C/C++
- Modern C++ Language Features
- Comparisions of push_back() and emplace_back() in C++ std::vector
- C++ Coding Reference: is_sorted_until() and is_sorted()
- C++ Coding Reference: iota() Setting Incrementing Values to Arrays or Vectors
- C++ Coding Reference: next_permutation() and prev_permutation()
- C++ Coding Reference: count() and count_if()
- C++ Code Reference: std::accumulate() and Examples
- C++ Coding Reference: sort() and stable_sort()
- The Next Permutation Algorithm in C++ std::next_permutation()
–EOF (The Ultimate Computing & Technology Blog) —
Last Post: const vs constexpr in C++