32. Move vs Copy
In a matching engine, message objects carry strings and vectors that trigger heap allocations. Under nanosecond budgets, avoiding unnecessary copies and allocator churn is critical.
struct Msg { std::string sym; std::vector<int> lots; };
void publish(std::vector<Msg>& out, Msg m){ out.push_back(std::move(m)); }
int main(){
Msg m{"AAPL",{1,2,3}}; std::vector<Msg> q;
publish(q, m);
publish(q, std::move(m));
}
Part 1.
Describe the observable effects on m after each call and count Msg-level copies/moves into q. Propose a more efficient publish signature for rvalues.
Part 2.
(1) When is pass-by-value plus moving preferable to T&& in hot paths?
(2) How does const on parameters inhibit moves and affect performance?
(3) Why should Msg's move operations be noexcept for vector<Msg> growth?
(4) When does emplace_back outperform push_back for move-enabled types?
(5) What are the valid postconditions of a moved-from Msg?
Answer
Answer (Part 1)
First call publish(q, m) performs one copy to the by-value parameter, then one move into q; original m remains unchanged. Second call publish(q, std::move(m)) performs two moves; m is valid but unspecified (typically empty sym/lots).
Prefer constructing directly in-place from rvalues: void publish(std::vector<Msg>& out, Msg&& m) noexcept { out.push_back(std::move(m)); } or a forwarding form: template<class... A> void publish(std::vector<Msg>& out, A&&... a){ out.emplace_back(std::forward<A>(a)...); }.
Answer (Part 2)
(1) When you must make an owning copy internally anyway. Otherwise T&& avoids an extra move for rvalues.
(2) std::move on const T yields const T&&, selecting copy. Const blocks moving assignments/constructors, increasing copies.
(3) Without noexcept, vector may copy on reallocation. With noexcept, it moves elements, reducing latency and allocations.
(4) When constructing from arguments, avoiding a temporary. For T&&, push_back(std::move(t)) and emplace_back are typically equivalent.
(5) It remains valid but unspecified. You may destroy, assign, or reinitialize; only invariants like empty() may hold.