Don't blindly prefer `emplace_back` to `push_back` (2021)
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