Two final items that don't fit neatly into the earlier chapters, but are critical for writing efficient modern C++: when pass-by-value is reasonable, and when emplacement beats insertion.
You're writing a function that always copies its parameter into a data structure. You want it to be efficient for both lvalues (copy) and rvalues (move). What's the best approach?
c++ class Widget { public: // Approach 1: Overload for lvalues and rvalues void addName(const std::string& newName) { names.push_back(newName); } // copy void addName(std::string&& newName) { names.push_back(std::move(newName)); } // move // Approach 2: Universal reference template template<typename T> void addName(T&& newName) { names.push_back(std::forward<T>(newName)); } // Approach 3: Pass by value (!) void addName(std::string newName) { names.push_back(std::move(newName)); } private: std::vector<std::string> names; };
std::move(newName) inside the function body?The title of Item 41 is very precise: "Consider pass by value for copyable parameters that are cheap to move and always copied." Every word matters.
c++ Widget w; std::string name("Bart"); w.addName(name); // lvalue: copy-construct param + move into vector w.addName(name + "Jenne"); // rvalue: move-construct param + move into vector
unique_ptr), you only need one overload (rvalue ref). No benefit from pass-by-value.std::array), the extra move is costly. Only use when moves are O(1).std::unique_ptr?Let's count copy and move operations for each approach when adding a name to a Widget.
Click to compare costs for lvalue vs rvalue arguments.
| Approach | Lvalue Cost | Rvalue Cost |
|---|---|---|
| Overloading | 1 copy | 1 move |
| Universal ref | 1 copy | 1 move |
| Pass by value | 1 copy + 1 move | 2 moves |
std::string and std::vector, one extra move is typically negligible. But it adds up in function call chains (see next chapter).Pass by value has hidden costs that the simple "one extra move" analysis misses.
c++ class Password { std::string text; public: void changeTo(std::string newPwd) // pass by value { text = std::move(newPwd); } // assignment, not construction }; std::string initPwd("Supercalifragilisticexpialidocious"); Password p(initPwd); std::string newPwd("Beware the Jabberwock"); p.changeTo(newPwd); // copies newPwd, then move-assigns to text // But text already had enough capacity! With by-ref, no allocation needed.
const string&), assignment can reuse the existing buffer if it's big enough. Two memory operations vs zero.c++ // Each function takes by value: "only one extra move" void addName(std::string name) { validateName(name); // another by-value copy! ... } void validateName(std::string name) { logName(name); // yet another! ... } // "One extra move" per function * N functions = N extra moves // By-reference chains incur zero accumulated overhead
c++ class Widget { ... }; class SpecialWidget : public Widget { ... }; void process(Widget w); // pass by value: SLICES derived types! SpecialWidget sw; process(sw); // sw's SpecialWidget-ness is lost
| Scenario | Best Approach | Why |
|---|---|---|
| Copyable, cheap-to-move, always copied, no slicing | Pass by value | Simple, one function, minimal overhead |
| Move-only types (unique_ptr, future) | Rvalue ref overload | Only one overload needed anyway |
| Assignment-based copying (setters) | Overloading or universal ref | Can reuse existing buffer capacity |
| Function call chains | By reference | No accumulated move overhead |
| Base class parameters | By reference | Avoids slicing |
| Maximum performance needed | Overloading or universal ref | Zero extra overhead |
When you add a string literal to a vector<string> via push_back, a temporary std::string is created. emplace_back can avoid that entirely.
c++ std::vector<std::string> vs; vs.push_back("xyzzy"); // creates temp string, then moves it in vs.emplace_back("xyzzy"); // constructs string DIRECTLY in the vector
std::string from "xyzzy" (construction #1)std::string directly in the vector from "xyzzy" (one construction, zero temporaries)Every container has emplacement counterparts: emplace_back, emplace_front, emplace, emplace_hint, emplace_after.
c++ // emplace_back forwards any arguments to the string constructor vs.emplace_back(50, 'x'); // constructs string(50, 'x') directly in-place
push_back and emplace_back?In theory, emplacement is never slower than insertion. In practice, it depends. Here's a heuristic for when emplacement is most likely to win:
| Condition | Why It Matters |
|---|---|
| Value is constructed into container, not assigned | Assignment into occupied slots may create a temporary anyway (the element to move from) |
| Argument types differ from container type | If you're already passing a std::string to a vector<string>, no temporary is needed for insertion either |
| Container won't reject as duplicate | Emplacement creates a node to compare, then destroys it if rejected — wasted construction |
emplace_back and emplace_front always construct.| Container Type | Node-based? | Uses Construction? |
|---|---|---|
| std::list, std::forward_list | Yes | Always (new nodes) |
| std::map, std::set | Yes | Always (new nodes) |
| std::unordered_map, std::unordered_set | Yes | Always (new nodes) |
| std::vector | No | emplace_back: yes. emplace at middle: may assign. |
| std::deque | No | emplace_back/front: yes. Middle: may assign. |
| std::string | No | Depends on position |
c++ std::vector<std::regex> regexes; regexes.push_back(nullptr); // ERROR: won't compile (implicit conversion rejected) regexes.emplace_back(nullptr); // COMPILES! Direct init uses explicit ctor. UB at runtime. // Because: std::regex r1 = nullptr; // copy init: explicit ctor blocked → error std::regex r2(nullptr); // direct init: explicit ctor allowed → compiles (UB)
| Syntax | Initialization Type | Explicit Ctors? | Example |
|---|---|---|---|
T x = expr; | Copy initialization | Blocked | std::regex r = nullptr; (error) |
T x(expr); | Direct initialization | Allowed | std::regex r(nullptr); (compiles, UB) |
push_back(expr) | Copy initialization | Blocked | Safe: rejects bad conversions |
emplace_back(expr) | Direct initialization | Allowed | Risky: accepts bad conversions |
c++ std::list<std::shared_ptr<Widget>> ptrs; // push_back: safe. Temporary shared_ptr created BEFORE push_back. // If allocation fails, shared_ptr destructor cleans up. ptrs.push_back(std::shared_ptr<Widget>(new Widget, killWidget)); // emplace_back: UNSAFE. Raw pointer forwarded into container. // If node allocation throws, raw pointer is leaked! ptrs.emplace_back(new Widget, killWidget); // Fix: create the shared_ptr first, then emplace the rvalue auto spw = std::shared_ptr<Widget>(new Widget, killWidget); ptrs.emplace_back(std::move(spw));
regexes.emplace_back(nullptr) compile while regexes.push_back(nullptr) doesn't?See how the three parameter-passing approaches compare, and how emplacement vs insertion differs.
Adding "xyzzy" to a vector<string>. Click to compare.
Three approaches to addName, compared for lvalue and rvalue.
emplace at an occupied position), emplacement may still need a temporary.std::string to a vector<string>, insertion needs no temporary anyway.c++ std::vector<std::string> vs; std::string queen("Donna Summer"); // Same type passed — no temporary either way vs.push_back(queen); // copy-construct into vector vs.emplace_back(queen); // also copy-construct. No difference. // Emplacement at occupied position — may need temporary for move-assign vs.emplace(vs.begin(), "xyzzy"); // move-assigns, may create temp
push_back, how many constructor calls occur?| Item | Key Takeaway |
|---|---|
| Item 41 | Consider pass by value for copyable, cheap-to-move params that are always copied. One extra move vs by-ref. Don't use for assignment-based copying, call chains, or base classes. |
| Item 42 | Emplacement constructs in-place (constructor args, not objects). Fastest when constructing into container with non-matching types and no duplicates. Watch out for explicit ctors and exception safety. |
| Container | Insertion | Emplacement |
|---|---|---|
| vector, deque, string | push_back, push_front*, insert | emplace_back, emplace_front*, emplace |
| list | push_back, push_front, insert | emplace_back, emplace_front, emplace |
| forward_list | push_front, insert_after | emplace_front, emplace_after |
| set, map, multiset, multimap | insert | emplace, emplace_hint |
| unordered_set, unordered_map | insert | emplace, emplace_hint |
* push_front/emplace_front not available for vector and string.
| Ch | Items | Topic |
|---|---|---|
| 1 | 1-4 | Deducing Types |
| 2 | 5-6 | auto |
| 3 | 7-17 | Moving to Modern C++ |
| 4 | 18-22 | Smart Pointers |
| 5 | 23-30 | Rvalue Refs, Move, Forwarding |
| 6 | 31-34 | Lambda Expressions |
| 7 | 35-40 | The Concurrency API |
| 8 | 41-42 | Tweaks |
"The most important part of each Item is not the advice it offers, but the rationale behind the advice." — Scott Meyers