Don't blindly prefer `emplace_back` to `push_back` (2021)
C++ developers debate when to use `emplace_back` instead of `push_back` in standard containers, noting that while `emplace_back` can construct objects in place and avoid copies, it also introduces more complex semantics and potential for subtle bugs. Several argue that `push_back` is clearer and usually sufficient, especially when constructor behavior or implicit conversions aren’t obvious to readers or reviewers, and that tools which blindly suggest replacing it can degrade code quality or performance. The exchange broadens into reflections on C++’s growing complexity, the limits of compiler optimizations, and how other languages like Rust, Go, or C# handle object construction and ownership more transparently.
Semantics of push_back vs emplace_back
push_backtakes an already-constructed object and copies/moves it into the container.emplace_backforwards constructor arguments and constructs the element directly in the container’s storage.- Example given: with a type that logs constructors,
emplace_back(args…)calls only the relevant ctor, whilepush_back(T(args…))calls the value ctor plus a move/copy ctor, and may duplicate work inside subobjects (e.g.,std::stringdata).
Correctness, readability, and intent
- Several commenters prefer
push_back(T(...))for clarity: it’s explicit about which type is being constructed. emplace_back(args…)can be ambiguous without knowing the container’s element type and its constructors.- A key pitfall: for
std::vector<std::vector<int>>,emplace_back(1<<20)constructs a huge inner vector instead of appending an int;push_back(1<<20)would fail to compile, which is safer. - Suggested rule of thumb:
- Use
push_backwhen you already have an object or want aggregate/designated initialization. - Use
emplace_backwhen constructing directly in-place, especially for non-copyable or expensive-to-copy types.
- Use
Tooling and compiler behavior
- Older
clang-tidychecks (“modernize-use-emplace”) encouraged replacingpush_backwithemplace_back, sometimes inappropriately; newer versions can now warn about unnecessary temporaries even withemplace_back. - Compilers can often elide temporaries, but not when copies/moves have observable side effects;
emplace_backexpresses intent rather than relying on optimization. emplace_backis a template, so may marginally increase compile times compared topush_back.
Performance and “real-world” impact
- Some argue the micro-performance difference is negligible in many domains (e.g., GUI construction) and that time spent on such minutiae is overblown.
- Others respond that understanding and using the right tool improves code quality and maintains invariants (e.g., for non-copyable types), even when speed isn’t critical.
Broader language and ecosystem commentary
- Discussion branches into C++ complexity (rvalue refs, value categories) and whether this mental overhead is justified.
- Comparisons are made with Rust, Go, and C#:
- Rust also wrestled with placement APIs and uses
MaybeUninitpatterns instead. - Go/C# are seen as simpler, but less powerful in some scenarios.
- Rust also wrestled with placement APIs and uses