Move Semantics (Ownership Transfer)

cardimg

In C++, copying an object (Deep Copy) is expensive—it involves allocating new memory and copying data byte-by-byte.

Move Semantics (introduced in C++11) allows you to transfer ownership of resources (like memory pointers or file handles) from one object to another without copying the actual data.

Think of it as "Stealing" the resources.

  • Copy: I photocopy your notes. Now we both have sheets of paper. (Slow)
  • Move: I take the notes from your hand. I have them, you have nothing. (Fast).

Syntax & Usage

1. R-value References (&&)

To distinguish between "Copyable" and "Movable" objects, C++ uses &&.

  • L-value (Type&): A persistent variable (x). You Copy it.
  • R-value (Type&&): A temporary object (Type()) or explicitly moved object. You Move it.

2. Move Constructor & Assignment

Instead of allocating new RAM, we just copy the pointer and set the old pointer to nullptr.

class Buffer {
    uint8_t* ptr;
public:
    // Move Constructor
    Buffer(Buffer&& other) noexcept {
        this->ptr = other.ptr;  // 1. Steal the pointer
        other.ptr = nullptr;    // 2. Nullify the source (Prevent double-free)
    }

    // Move Assignment
    Buffer& operator=(Buffer&& other) noexcept {
        if (this != &other) {
            delete[] ptr;           // Free my current memory
            this->ptr = other.ptr;  // Steal
            other.ptr = nullptr;    // Nullify
        }
        return *this;
    }
};

3. std::move

Forces a move on a named variable.

Buffer b1(100);
Buffer b2 = std::move(b1); // b2 owns the memory. b1 is now empty.

Copy vs. Move Visualization

FeatureCopy (const Type&)Move (Type&&)
OperationDuplicate Data (Deep Copy).Transfer Pointer (Shallow Copy).
Old ObjectRemains valid and unchanged.Valid but Empty (gutted).
CostHigh (alloc + memcpy).Near Zero (pointer assignment).
HardwareDuplicates logic (Bad for drivers).Transfers ownership (Good for drivers).

Relevance in Embedded/Firmware

1. High-Performance Returns

In C, to avoid copying a large struct, we pass pointers: void get_data(BigStruct* out).

In C++, Move Semantics allow you to return by value efficiently.

// Returns a heavy object (e.g., 1KB buffer)
Packet receive_packet() {
    Packet p;
    // ... fill p ...
    return p; // Compiler automatically Moves this out. Zero overhead.
}

2. Driver Ownership (std::unique_ptr)

Hardware drivers (UART, SPI) are "non-copyable" (you can't clone a physical peripheral). However, you often want to move a driver around (e.g., create it in init() and pass it to a Manager class).

Move semantics allow this safe transfer of ownership without cloning.

3. Container Performance

If you have a std::vector<String>, resizing the vector requires moving all elements to a new memory block.

  • Without Move: Every string is malloc'd and copied. (Slow, heap fragmentation).
  • With Move: Only pointers are swapped. (Fast, no heap churn).

Common Pitfalls (Practical Tips)

PitfallDetails
❌ Use After Move

Accessing an object after moving from it is undefined logic (usually crashes).

auto b2 = std::move(b1); b1.send(); -> Crash (b1 is empty).

std::move doesn't movestd::move(x) generates no code. It effectively just casts x to x&&, telling the compiler "Please try to use the Move Constructor." If you didn't write a Move Constructor, it silently falls back to Copy.
noexceptAlways mark move constructors as noexcept. If a move can throw an exception, standard containers (like vector) will refuse to use it and will Copy instead.
✅ Rule of FiveIf you write a custom Destructor, you typically need: Copy Ctor, Copy Assign, Move Ctor, Move Assign.

 

 

 

 

Concept understood? Let's apply and learn for real

Practice now