Quant Memo
Core

Move Semantics and Zero-Copy

Move semantics let C++ transfer ownership of a large object's underlying data by stealing a pointer instead of copying every byte, which is the difference between a constant-time handoff and an expensive duplication.

Prerequisites: STL Container Performance

Before C++11, returning a large std::vector from a function, or passing one into a container, meant copying every element — allocate new memory, copy each byte, then destroy the original. For a vector of a million order-book updates, that's a real, measurable cost paid every time ownership needed to change hands, even though the data itself never actually needed to move in memory.

Move semantics fixed this by giving C++ a second way to transfer an object: instead of copying the contents, steal the internal pointer, size, and capacity from the source object and leave the source in an empty, valid-but-unspecified state. The new object now owns exactly the same block of memory the old one did — nothing was duplicated, only the ownership record changed. This is the "zero-copy" idea: the underlying bytes never move, only who's responsible for them.

A copy duplicates data; a move duplicates a pointer. For a std::vector<double> holding a million elements, a copy is a million-element memcpy — genuinely O(n)O(n) work — while a move is three pointer/integer assignments, O(1)O(1) regardless of how large the vector is.

What actually happens underneath

copy buffer A (1M doubles) buffer A copy (1M doubles) every byte duplicated — O(n) move buffer A (1M doubles) old owner → new owner → same buffer, pointer reassigned — O(1)
A copy allocates and duplicates the whole buffer; a move re-points ownership at the exact same memory.

Worked example

std::vector<double> loadPrices(int n) {
    std::vector<double> prices(n);
    // ... fill with a million prices ...
    return prices;   // moved out, not copied
}

std::vector<double> book = loadPrices(1'000'000);   // move construction
std::vector<double> backup = book;                   // copy — book is still needed
std::vector<double> archived = std::move(book);      // explicit move — book is now empty

Returning prices from loadPrices triggers a move (compilers do this automatically for local variables returned by value), so no million-element copy happens at the function boundary. backup = book is a real copy because book is still a named variable the program plans to use again — the compiler can't safely steal from it. std::move(book) is the programmer explicitly saying "I'm done with book, you can steal its buffer" — after that line, book is guaranteed to be in a valid but unspecified state (for std::vector, empty), and using it for anything but reassignment is a bug.

What this means in practice

Move semantics are why modern C++ containers can be passed around, returned from functions, and stored in other containers without the copy-everything tax that made pre-C++11 code defensively pass everything by reference. In a trading system, this matters most for large market-data structures — an order book snapshot, a batch of parsed ticks — where a copy at the wrong point in a hot path can add microseconds a competitor's move-based code doesn't pay.

The same idea generalizes past std::vector. Any type that manages a resource — a unique_ptr's owned memory, a file handle, a socket — can define its own move constructor, and the standard library types you already use (std::string, std::map, std::function) all do. That's why swapping a pre-C++11 codebase's return-by-value functions for return-by-value functions in modern C++ often speeds things up with no source change at all: the compiler was already free to move instead of copy, it just needed a move constructor to exist for the type in question.

Using an object after std::move-ing it is a classic bug, not a compile error — the moved-from object is still a legal object (usually empty), so the code compiles and runs, but silently operates on the wrong data. std::move does not itself move anything; it only casts the object to an rvalue reference, making it eligible to be moved by whatever receives it. If nothing actually consumes that rvalue, no move happens at all.

Related concepts

Practice in interviews

Further reading

  • Meyers, Effective Modern C++ (items 23-30)
ShareTwitterLinkedIn