Step 4 of 7
Return by value is free
A common worry is that returning a big object from a function is slow, so people return pointers or fill in "out" parameters instead. In modern C++ that worry is outdated.
- Returning a temporary (
return Widget(...);): C++17 guarantees the object is built directly in the caller's variable. No copy and no move happen at all. This is called copy elision. - Returning a named local (
Widget w; ...; return w;): compilers almost always build it directly in place too (the "named return value optimization"). When they can't, the value is moved, not copied.
#include <iostream>
#include <string>
struct Loud {
std::string tag;
explicit Loud(std::string t) : tag(t) { std::cout << "construct " << tag << "\n"; }
Loud(const Loud& o) : tag(o.tag) { std::cout << "COPY " << tag << "\n"; }
};
Loud make_direct() {
return Loud("direct"); // guaranteed: built right in the caller
}
Loud make_named() {
Loud l("named");
l.tag += "!";
return l; // NRVO in practice
}
int main() {
Loud a = make_direct();
Loud b = make_named();
std::cout << a.tag << " " << b.tag << "\n";
Loud c = a; // a real copy, because we asked for one
}
construct direct
construct named
direct named!
COPY direct
Reading the output
- Each function constructs exactly once, and there's no
COPYline foraorb: the objects were built directly inmain's variables. - The only copy is
Loud c = a;, where copying is exactly what the code asks for, sinceais still alive.
What this means for your code
- Return by value. It's the clearest way to write a function that makes something, and it's cheap.
- Don't write
return std::move(local);. It prevents copy elision, forcing a move where there would have been nothing at all. Compilers warn about it (-Wpessimizing-move). - Returning a temporary directly, like
return Tracker(id);, is the case the language guarantees.
Your task
The Tracker struct counts copies. Write Tracker make_tracker(int id) so that creating one via your function performs zero copies. The test prints the copy count.
Challenge 2
Write Tracker best_of(int a, int b) that creates two local trackers, Tracker x(a) and Tracker y(b), and returns the one with the bigger id, with zero copies. Returning a local variable by name moves it, but return cond ? x : y; does not: that expression is a plain lvalue, so it's copied.
Challenge 3
Write std::vector<Tracker> make_team(int n) returning trackers with ids 1 to n, making zero copies. This Tracker has no move constructor, so push_back(Tracker(i)) copies, and so does the vector when it grows. Build each tracker in place with emplace_back, and reserve room first.
Previous: Move assignment and the rule of five Next: Moved-from objects