7 ms·
Don't blindly prefer `emplace_back` to `push_back` (2021)
- 38 2y ago[flagged]
- mystified5016 2y agoDon't use Go, use C#. The same code is even more terse: 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.
- deleted 2y ago[deleted]
- Seattle3503 2y agoI write Rust mostly, but still found the article interesting and accessible.
- FpUser 2y ago>or maybe just dont use C++ Or maybe use whatever the fuck you want and let other to decide for themselves. Often people even do not have a choice.
- Joker_vD 2y ago> Or maybe use whatever the fuck you want and let's 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.
- gumby 2y agoWTF is "modern syntax"?
- Joker_vD 2y agoProbably "the types go after the variable name in the declaration"? But even that actually pre-dates C.
- deleted 2y ago[deleted]
- mgaunard 2y agoClearly you didn't understand what emplace does. Go is simpler because it is limited in functionality.
- IshKebab 2y agoIf you want simple you can just you push_back everywhere. It's what everyone did for decades.
- jcelerier 2y agoThis is either making a copy or storing things as pointers with an indirection penalty, how do you do in go if you want to add an object to a container without making a copy or without indirection
- leecommamichael 2y agoI’m not interested in who you’re replying to, but your question seems to imply there’s some way to add data to a container without already having some copy. That’s only possible if the container (and it’s data) is static. 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.
- pavlov 2y ago> “some way to add data to a container without already having some copy” 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.
- leecommamichael 2y agoI'm interested in the semantics of "if needed" and "constructs in place." 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.
- jcelerier 2y agobasically: #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
- hudsonhs 2y ago[flagged]
- Joker_vD 2y agoSo emplace_back() exists to forward the contructor's arguments to the place where the object can be constructed in-place while push_back() takes an already constructed object and (hopefully) moves it to where its needed. 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.
- immibis 2y agoThat's sort of what happens with named return value optimization. You write this: 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.
- Joker_vD 2y ago> This optimization is guaranteed to occur. 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.
- Arnavion 2y ago>This optimization is guaranteed to occur. Compilers which don't do this are nonconforming. NRVO is not guaranteed to occur, only unnamed RVO (`bar foo() { return bar(...) ; }`) is.
- rtpg 2y agoI really appreciate Rust's copy/clone semantics when reading this. The "hopefully" is so worrying. 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.
- 2y ago
- a1o 2y agoCLion does the correct replacement on it's right click fix actions. Just if anyone using is reading.
- the_mitsuhiko 2y agoAnd what is the right fix? Does it recommend std::move with push_back or does it use emplace?
- a1o 2y agoThe one from clang-tidy the author mentions.
- the_mitsuhiko 2y agoI personally consider that to be a bad recommendation and my understanding is that the author also objects to it. I would bet most people are unaware of how emplace actually works and for a reviewer it's more cognitive overhead too.
- sumeno 2y ago> I would bet most people are unaware of how emplace actually works and for a reviewer it's more cognitive overhead too. If you are doing professional development in C++ you should learn the difference and how to recognize incorrect usages
- the_mitsuhiko 2y agoI'm unconvinced. emplace is inherently more complex and requires a complete understanding of the relevant constructors of the value type in the collection. push_back is much less surprising. 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.
- wrsh07 2y ago> I recommend sticking with push_back for day-to-day use. 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 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
- CamperBob2 2y agopush_back always sounds clunky and dissonant to me. Gotta wonder what was wrong with ".append" -- guess it wasn't applicable to stack-based containers or something.
- wrsh07 2y agoI wonder if they designed the APIs for vector, list, deque, etc together On deque it all feels fairly natural. Ruby's shift and unshift are cool, but I always struggle to remember the words
- kragen 2y agoyes, they did 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
- lupire 2y ago
- knorker 2y agoI kinda disagree with advise. Use emplace when you're passing args to the constructor. Saying to use push back for day to day use either says that you don't encounter this day to day (you probably do), or that you shouldn't bother understanding the difference. You should.
- bibouthegreat 2y ago[flagged]
- deleted 2y ago[deleted]
- strken 2y agoThis blog post is about C++.
- _zagj 2y agoO'Dwyer's "Back to Basics" talks on YouTube are worth a watch for anyone looking to get up to speed on modern C++.
- harry8 2y agohttps://www.youtube.com/playlist?list=PLHTh1InhhwT4CTnVjJqnAKeMfGzOWjsRa https://www.youtube.com/playlist?list=PLHTh1InhhwT4CTnVjJqnA...
- addicted 2y agoI’m no C++ expert but couldn’t what emplace_back does not be achieved through compiler optimizations?
- mattnewton 2y agoConstructors are just functions at the end of the day and they can do anything. Not really practical to decide when to invoke a constructor for the author in a language with semantics as complicated as C++. The convention there is to trust the author to be explicit with exactly where they want to point the gun and not try to move it from the author’s feet.
- binary132 2y agoNo, the signature of push_back accepts an object reference, while the signature of emplace_back accepts _arguments to forward to the element type’s constructor_. That means that pushback expects you to construct the object, while emplaceback wants to construct the object internally. It’s just that there’s usually a move constructor that accepts an object rvalue reference, so that case looks like it resembles the pushback API, if that makes sense....
- rocqua 2y agoThe additional copy used by push back can be observable (because the copy constructor is just a function, which is very much allowed to have side effects). That means the compiler isn't allowed to optimize out the copy.
- gpderetta 2y agoIn principle The Sufficiently Good compiler can do everything up-to the as-if rule. In practice most of the time the temporary object is optimized away. But if the object is very large or the copy has side effects, thing are harder or impossible and those are probably the times where you need the optimization the most.
- exitb 2y agoThe article makes it more complex than it needs to be. push_back and emplace_back have different goals. The former works with existing objects, the latter gets used when you intend to construct a new one, right in the collection. 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.
- sfink 2y agoI would summarize it as: use `push_back` when you're pushing something, use `emplace_back` when you want to construct something in the space at the end of the collection. It just so happens that their functionality overlaps, and there really isn't a strong reason to use `push_back(Widget(ctor_arg1, ctor_arg2))` vs `emplace_back(ctor_arg1, ctor_arg2)` with a movable `Widget`, so it comes down to readability and communicating intention. And those will depend on the situation. 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".
- layer8 2y agoMaybe construct_back would have been a better name.
- jeffreygoesto 2y agoIn our pre-C++11 library we had containers with a append_uninitialized_element() and called placement new on that with an explicit ctor. While syntactically phony it had an unambigous semantic. Code review had to check (remember, pre clang-tidy times) that the method way never used alone.
- tmyklebu 2y agoCompile time is another reason to prefer push_back, which is a function, to emplace_back, which is a function template.
- DennisL123 2y agoLanguage clunkiness has jumped the shark.
- abbeyj 2y ago> The student dutifully changed the line, and both tools reported their satisfaction with the replacement 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/sE7jWacTf https://godbolt.org/z/sE7jWacTf It 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.
- throwaway2037 2y agoWhat is the Real World impact of a change like this? From the sample code, the person is building some kind of GUI with widgets. How many extra assembly instructions are performed as a result of this particular push_back vs emplace? In my experience with C++ and Qt, the amount of execution time spent building the GUI trivial compared to the paint (or business logic). 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.
- valicord 2y agohttps://en.wiktionary.org/wiki/widget#Noun https://en.wiktionary.org/wiki/widget#Noun "A placeholder name for an unnamed, unspecified, or hypothetical manufactured good or product, typically as an example for purposes of explaining concepts."
- db48x 2y agoIt only has a measurable cost if the constructor is slow, and for most types that won’t be the case.
- nercury 2y agoIf someone spent their time learning their tools, they will make better choices when writing the code without any additional time cost. 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.
- otabdeveloper4 2y agoemplace_back is mostly used for objects that are constructed only once and forbidden from being copied. It's not so much a performance tool as a tool for consistency guarantees.
- cjensen 2y agoSometimes I think rvalue-references were a bad idea. Sure they improve efficiency for people using objects directly instead of through a pointer, but the amount of mental model added worries me that the average coder will have no real clue what rvalue references are. People really overestimate how much most coders understand.
- 112233 2y agoAgree. Let's introduce xvalue, glvalue and prvalue to language spec. Let's define special cases for "auto". Then add some more ( decltype(auto) ). Let's add && overloading. Since it does not work automatically anyway, add move and forward. Now let's blame programmer for calling or not calling move or forward in specific place, because obviously when implementing algorithm the implicit lexical lifetimes of temporaries are what every programmer should focus his attention on.
- gpderetta 2y agoThey are quite complex and with a lot of ugly corners. I cannot but think that there must be a better solution. 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).
- bun_terminator 2y agoThe reason I find myself preferring push_back is that I can use aggregate init with it. Which is usually preferable to the uglier but faster emplace_back in places where it doesn't matter.
- quuxplusone 2y agoYou're doing the right thing. 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-parens-init-considered-kinda-bad/ https://quuxplusone.github.io/blog/2022/06/03/aggregate-pare...
- bun_terminator 2y agoI meant designated init in hindsight
- nurettin 2y agoFor large data frames, I just use std::move if need the object to be owned by the vector or map for indexed access. If you don't want to make a mistake, use noncopyable to make sure.
- petters 2y agoThe suggestion by Clang seems perfectly fine. > 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.
- bjgiusg 2y ago[dead]
- dang 2y agoDiscussed at the time: Don’t blindly prefer emplace_back to push_back - https://news.ycombinator.com/item?id=26339893 https://news.ycombinator.com/item?id=26339893 - March 2021 (140 comments)