๐ 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
#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
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.
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
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
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:
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.
#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_ptrto another. - The source
shared_ptrbecomes empty (use_count() == 0). - The reference count remains unchanged overall (still 1, unless there are other shared owners).
โ Example: Transferring ownership via std::move()
#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_ptrto transfer ownership (leaving the old one empty). - You can copy a
shared_ptrto share ownership (both remain valid and share control block). - Never use
std::move()unless you want the sourceshared_ptrto become null.
๐ Common Patterns
Moving from function return:
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:
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.
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
- Understanding std::transform_reduce in Modern C++
- Implement a Lock Acquire and Release in C++
- Detecting Compile-time vs Runtime in C++: if consteval vs std::is_constant_evaluated()
- C++ Forward References: The Key to Perfect Forwarding
- Understanding dynamic_cast in C++: Safe Downcasting Explained
- C vs C++: Understanding the restrict Keyword and its Role in Optimization
- C++ Lvalue, Rvalue and Rvalue References
- C++ assert vs static_assert
- Why auto_ptr is Deprecated in C++?
- C++ What is the consteval? How is it different to const and constexpr?
- 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++
Next Post: C++ what is the consteval? How is it different to const and constexpr?