Question.7
A developer defines a destructor and copy constructor for a buffer class but does not provide move operations. What is the consequence of this implementation?
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.
1. R-value References (&&)
To distinguish between "Copyable" and "Movable" objects, C++ uses &&.
Type&): A persistent variable (x). You Copy it.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.| Feature | Copy (const Type&) | Move (Type&&) |
|---|---|---|
| Operation | Duplicate Data (Deep Copy). | Transfer Pointer (Shallow Copy). |
| Old Object | Remains valid and unchanged. | Valid but Empty (gutted). |
| Cost | High (alloc + memcpy). | Near Zero (pointer assignment). |
| Hardware | Duplicates logic (Bad for drivers). | Transfers ownership (Good for drivers). |
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.
| Pitfall | Details |
|---|---|
| ❌ Use After Move | Accessing an object after moving from it is undefined logic (usually crashes).
|
❌ std::move doesn't move | std::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. |
❌ noexcept | Always 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 Five | If you write a custom Destructor, you typically need: Copy Ctor, Copy Assign, Move Ctor, Move Assign. |