Don't blindly prefer `emplace_back` to `push_back` (2021)
quuxplusone.github.io
quuxplusone.github.io
clang-tidy's `modernize-use-emplace` check warns for both the original version that uses `push_back` (as quoted in the article) and for the modified version that uses `emplace_back`:
warning: unnecessary temporary object created while calling emplace_back [modernize-use-emplace]
13 | widgets.emplace_back(Widget(foo, bar, baz));
| ^~~~~~~ ~
https://godbolt.org/z/sE7jWacTfIt appears that this check was improved to add this warning at some point between clang v14 and now. At least things have improved since the article was written.
This Google tip of the week gives a good explanation (explicit vs implicit) for why to prefer push back vs emplace if you don't need the benefit of emplace: https://abseil.io/tips/112
In particular:
> Let me answer that question by asking another: what do these two lines of code do?
vec1.push_back(1<<20); vec2.emplace_back(1<<20);
> The first line is quite straightforward: it adds the number 1048576 to the end of the vector. The second, however, is not so clear. Without knowing the type of the vector, we don’t know what constructor it’s invoking, so we can’t really say what that line is doing
On deque it all feels fairly natural. Ruby's shift and unshift are cool, but I always struggle to remember the words
ruby and js got shift and unshift from perl, which got shift from sh, where it's the only way to iterate over an array or other list. (also, sh only has one array)
in the old country, instead of shift and unshift, we said cdr and cons. we didn't have push and pop; if we wanted to mess with the backside of a list we had to reverse it first. but our code ran ten times as fast as ruby and had implicit undo
at some point don hopkins suggested, i think it was, eat and barf for, respectively, push and pop on the front end. the back end would then necessarily be boof and shit (not shift). betcha wouldn't forget which end those pertained to
The thought was to have the symmetry of push and pop, on both ends of a sequence container. See table 11 on page 22 (actually page 24 of the PDF).
What I haven't gotten used to is the terms "shift" and "unshift" from other languages. But I never get confused by C++'s push_front/pop_front.
And I think the consistency has some value. Is it enough value to warrant the extra verbosity? Meh, I think it's more or less a wash.
A search for the opposite of "append" suggests "remove", "subtract", "detach", "disconnect", "disjoin", "unfasten", "reduce", "diminish", and more. None of them really have the same "at the back" connotation that "append" has.
"Push" and "pop" are well known from stacks so it makes sense to use them for operations on one end of list-like containers. That still leaves the problem of what to call the similar operations on the other end.
I suppose we could sidestep that problem by only providing them at one end. So say push() adds to the back and pop() removes from the back.
If you want to operate at the front just reverse the list, do your push or pop, and then reverse the list again and let the optimizer deal with eliminating the two reverses. :-)
Either of these are more explicit and clear to me:
widgets.push_back(Widget(foo, bar, baz));
widgets.emplace_back(Widget(foo, bar, baz));
... even if computationally worse.> Without knowing the type of the vector, we don’t know what constructor it’s invoking, so we can’t really say what that line is doing.
I am saying 1-arg constructors that aren't semantically "conversions" from that arg need to be be explicit, not implicit. If you make them implicit, that's your mistake; tools can catch that mistake statically.
I don't get the point of your question. You might as well ask: "Let's say someone in a library somewhere has defined their type's operator< to wipe your disk. If you see std::less<>()(a, b), can you say what the call does without knowing the types?"
To which the answer is... well, obviously, no. Same answer to your question.
What is this supposed to imply?
Someone has made an implicit constructor that makes a network call because... people are dumb. This implicit constructor takes an integer.
Does the following code make a network call?
vec.push_back(4);> Without knowing the type of the vector, we don’t know what constructor it’s invoking, so we can’t really say what that line is doing.
[1] https://en.cppreference.com/w/cpp/language/converting_constr...
How is this not caught by the type system? 2<<20 is clearly no way, whatsoever, compatible with a vector<int> type.
With push back it would be caught by the compiler because you can't implicitly convert 2<<20 to a vector<int>
You know, maybe forwarding the arguments is the wrong direction. Maybe there should have been the dual mechanism of back-propagating the ultimate destination of the object all the way back to its constructor instead.
bar foo() {bar baz; ...; return baz;}
bar qux = foo();
'baz' is constructed directly in the space allocated for 'qux'.
Implementation notes: the complex value is never returned but rather the caller passes the address of a space where the return value should be constructed. Inside the function, the compiler notes that baz is returned and allocates it in the return value space.
This optimization is guaranteed to occur. Compilers which don't do this are nonconforming.
I vaguely remember that there are some obscure corner cases when it's won't occur but I can't recall them since I haven't written C++ since 2016.
NRVO is not guaranteed to occur, only unnamed RVO (`bar foo() { return bar(...) ; }`) is.
Production code in Rust is littered with imperformant `.clone()`s but at least I can see where they're happening to ponder a better way.
You might say "Big deal, it's just a tiny loss of performance to create a value and then copy it into its final place. Unlike C++ this is guaranteed to only do a copy of bytes, no complex code like a copy ctor. Who cares, especially if it's only noticeable in debug builds?" But it's not just a problem of performance. If it's `Box::new([0_u8; 10 * 1024 * 1024])`, then that 10 MiB array created in the caller's stack can end up blowing the caller's stack.
Rust did actually try to add emplace style APIs and a dedicated operator. It would've looked like `vec.place_back() <- Widget::new();` and it would've guaranteed that the generated code did not create a copy in the caller frame. It was never stabilized and instead eventually removed, because it was still not reliable enough to provide that guarantee after all.
https://github.com/rust-lang/rfcs/blob/master/text/1228-plac...
https://doc.rust-lang.org/1.26.0/std/vec/struct.Vec.html#met...
https://github.com/rust-lang/rust/issues/27779#issuecomment-...
Box::<u8>::new_zeroed_slice(10 * 1024 * 1024) says we want 10MiB of zero bytes as a MaybeUninit inside a box, we can then (since these are just bytes in our example) assume_init() since that's valid for our type although in the real world probably we'd actually store some actual data in the memory we've allocated - but it doesn't go on the stack.
If we're overwriting it all anyway there's also an adjacent set of uninit functions to skip the zero step, although of course the OS might be zeroing the page anyway.
What I'm thinking of would be
```c++ struct MyBigClass { /* lots of members */ };
MyBigClass makeABigOne();
auto main() -> int { // We still need to construct a return slot here, even though we don't use the value, right? makeABigOne(); } ```
Perhaps this is tangential, but I'm wondering if maybe I'm missing a subtly based on what you mean by "allocated" (in the abstract machine, or in the ABI).
So yes, storage for the (to-be discarded) object must be allocated, and the object is constructed into that storage.
I don’t know enough about the ABI to comment about the last point.
The amount of time spent on these trivial issues in C++ never ceases to amaze me about the "C++ crowd". How many more "C++: Back to the basics" talks do we have to sit through? I await the C++ programmers whom will quickly reply to this post: "But, performance!"
That said, this author has appeared many times before on HN. He is an excellent writer.
"A placeholder name for an unnamed, unspecified, or hypothetical manufactured good or product, typically as an example for purposes of explaining concepts."
There are two variants of very similar code. Both do the same thing, both are readable and maintainable. The difference is not primarily in performance, it's in quality of craft.
It's not so much a performance tool as a tool for consistency guarantees.
The problem is that smarter and better programmes than me couldn't find one that would fit cleanly in the existing language after almost a decade of trying (boost had library based move emulation at the turn of the millennium, and the same authors came up with universal refs).
Mentioning `Widget` is definitely more explicit, so I agree with the author that `push_back(Constructor(...))` is usually better. Semantically, it's also usually simpler to think of "push a newly-constructed Widget" vs "construct a Widget in the next element of the vector and lengthen the vector to include it".
Don’t blindly prefer emplace_back to push_back - https://news.ycombinator.com/item?id=26339893 - March 2021 (140 comments)
If you are doing professional development in C++ you should learn the difference and how to recognize incorrect usages
I'm not a professional C++ developer, but I'm obviously required to use the language every once in a while and I vastly prefer code that requires me to have to look at fewer implementations of potentially implicit and hidden things.
> With Clang trunk on my laptop, I get consistently about 1.0s for the push version, and 4.2s for the emplace version.
Wow that is why we're not using C++ at work.
It happens to be that you can use emplace_back in place of push_back because copies and moves are just constructor overloads in C++. You shouldn't really use that, as it signals one intent, but does something else.
In the vec.push_back(Widget(a, b, c)) case the Widget is constructed first, then it gets pushed to the container. At this point the container checks if it has enough storage and expands its storage if it needs to. Then the Widget is copied/moved into the containers storage. So the ordering would be: construct, check, resize, move/copy.
While in the vec.emplace_back(a, b, c) case the container can check if it has space first before constructing the Widget directly inside the container. So the ordering would be: check, resize, construct.
So you would need some exceptionally special circumstances for this conversion from the push case to the emplace case to occur.
But it might be interesting to note that since C++20, emplace_back can also (kinda-sorta) involve aggregate initialization: https://quuxplusone.github.io/blog/2022/06/03/aggregate-pare...
Or maybe use whatever the fuck you want and let other to decide for themselves. Often people even do not have a choice.
And you know why they don't have the choice to not use C++? Because someone else made a choice to use C++ and so here we are. That's the paradox of having a freedom in chosing the language: only the first contributor has that freedom, everyone else either has to accept their decision, or leave.
Go is simpler because it is limited in functionality.
List<T> lst = new();
lst.Add(obj);
Done. Sure go is nice if you only care about being hip and trendy, but C# is better in more situations than you'd think. Why bother with new languages and absurd syntax when C# has been around for decades and has perfectly clear syntax that spells out exactly what you want in simple English?
Don't be an asshole. "Just use today's trendy language instead of crusty old C++" makes you an asshole. Stop it.
The discrimination is the penalty of copying a stack-value vs some GC’d/managed memory. Then if you compare those scenarios the semantics lead to more interesting topics for debate.
That’s what C++ vector emplace_back does. It allocates the memory if needed, then constructs the object in place using the provided arguments. No need for a copy.
I'm confused as to how you could initialize dynamic memory without a copy. I feel like initializing dynamic memory _is_ copying (memcpy), unless we're talking about some sort of fully constexpr thing.
#include <format>
#include <string>
#include <vector>
struct my_type {
std::string data;
my_type(int value): data(value, 'A')
{
puts("int ctor\n");
}
my_type() { puts("default ctor\n"); }
my_type(const my_type&) { puts("copy ctor\n"); }
my_type(my_type&&) noexcept { puts("move ctor\n"); }
my_type& operator=(const my_type&) { puts("copy assign\n"); return *this; }
my_type& operator=(my_type&&) noexcept { puts("move assign\n"); return *this; }
};
int main()
{
puts("Case A\n");
{
std::vector<my_type> vec;
vec.emplace_back(123);
}
puts("Case B\n");
{
std::vector<my_type> vec;
vec.push_back(my_type{123});
}
}
Here, in the first case (in a very schematic way), the vector:1/ allocates the memory for an element in for instance:
my_type* mem_begin = std::allocator<my_type>::allocate(...);
2/ calls std::construct_at(&mem_begin[0], 123);
which directly creates the object and calls my_type::my_type(int) constructor in the std::vector's memory storage. The only output you'll see will be int ctor
The inner std::string will also be initialized directly in the right memory position, at no point there will be a copy of, say, 10000 'A' characters.In the second case, first you construct my_type on the stack of the calling code so you get a first call to
my_type::my_type(int)
Then my_type is moved (or copied, if it didn't have move constructors): std::vector's implementation does pretty much the same thing, but the result is two constructions instead of one: my_data* mem_begin = std::allocator<my_data>::allocate(...);
...
std::construct_at(&mem_begin[0], instance_of_my_data_passed_in_argument);
which ends up calling my_type::my_type(my_type&&) ; you'll see int ctor
move ctor
and the inner string will be copied / moved too