Algorithms, Blockchain and Cloud

Tutorial on C++ std::move (Transfer Ownership)


📘 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 source shared_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

–EOF (The Ultimate Computing & Technology Blog) —

1274 words
Last Post: const vs constexpr in C++

The Permanent URL is: Tutorial on C++ std::move (Transfer Ownership) (AMP Version)

Exit mobile version