Most active commenters
  • MaxBarraclough(6)
  • shawnz(3)

←back to thread

178 points todsacerdoti | 31 comments | | HN request time: 1.249s | source | bottom
1. TonyTrapp ◴[] No.26340399[source]
Don’t blindly prefer emplace_back to push_back*

*when using it incorrectly. The premise of of emplace_back is that you use it for calling the constructor of the object in place. Obviously it won't help you if you call a copy constructor instead. I find this article a bit pointless. Clang's suggestion was spot-on and emplace_back would have (potentially) helped if the suggestion was actually followed correctly.

replies(4): >>26340422 #>>26340494 #>>26340600 #>>26341355 #
2. xKingfisher ◴[] No.26340422[source]
It's not bad advice. I see many code reviews that use emplace_back because it's 'faster'.

Though I think the abseil article does a better job of explaining why: https://abseil.io/tips/112

[Googler; unaffiliated with Abseil(sadly)]

3. pdpi ◴[] No.26340494[source]
Hence “blindly”. The blog post was a detailed, less sanctimonious version of your comment.
replies(2): >>26340514 #>>26340605 #
4. TonyTrapp ◴[] No.26340514[source]
But then you could write a blog post about pretty much every API misuse ever, which doesn't really seem very constructive to me.

But okay, maybe it's more obvious when having worked with pre-C++11 code, where you would have killed for something like emplace_back. If you are new to the language and only know modern C++, you probably don't think much about why emplace_back exists in addition to push_back, and why it can be faster.

replies(2): >>26340724 #>>26340893 #
5. beeforpork ◴[] No.26340600[source]
Actually, I am not so happy with clang-tidy: its first advice was spot-in, yes. But it should have thrown the exact same advice after the student incorrectly applied the first advised patch (by not removing the 'Widget(...)' constructor call).
replies(1): >>26343133 #
6. IshKebab ◴[] No.26340605[source]
Yeah but the title sounds like "you shouldn't just always use emplace_back instead of push_back" but the actual content is "you shouldn't use emplace_back completely incorrectly because you don't know what it is".

The only actual argument against blindly using emplace_back everywhere is that it has worse compile time in a deliberately pathological microbenchmark. Not convincing, though it is interesting.

7. xKingfisher ◴[] No.26340724{3}[source]
Given the whole scanf/strlen thing going on, more blog posts about API pitfalls wouldn't necessarily be a bad thing.

And that is basically the premise of the Abseil Tip of the week series, which are an incredible resource for learning how to work with modern C++.

8. pjmlp ◴[] No.26340893{3}[source]
Given that some schools still teach C++ with Turbo C++ for MS-DOS, one cannot expect that everyone is up to date with CppCon C++Now talks.
9. MaxBarraclough ◴[] No.26341355[source]
The article goes even further than that: When all else is equal, prefer push_back to emplace_back. It still shows the example of using emplace_back with a pre-existing object, which as you say, isn't the point of emplace_back. It also states that it's more work for the compiler, stating that the template resolution seems more complex, and suggesting it's likely to bloat compile times. That's a more persuasive point (hard numbers are provided at the end of the article), I wonder if more work has been done on this.

The author seems to be an advanced C++ programmer, but doesn't seem to properly acknowledge that, as you say, The premise of of emplace_back is that you use it for calling the constructor of the object in place. The only instance of proper use of emplace_back is in

    widgets.emplace_back(foo, bar, baz);
I was surprised to see no follow-up for this example:

    auto w = Widget(1,2,3);
    widgets.emplace_back(w);  // Fixed? Nope!
No mention of the obvious fix:

    widgets.emplace_back(1,2,3);
I don't mean to 'pile on', but, a pet peeve of mine: emplace_back is not magic C++11 pixie dust. This isn't saying anything. You could say garbage collection isn't magic, or error-correcting codes aren't magic, or sound type systems aren't magic. To say that something isn't magic is an empty statement, especially when it's something like emplace_back which introduces an important new ability.
replies(4): >>26341490 #>>26341620 #>>26342083 #>>26342447 #
10. Sebb767 ◴[] No.26341490[source]
> To say that something isn't magic is an empty statement

I think this is said to express "simply replacing that function call/method/... without changing your calls/methods/coding style is not going to magically solve your problems". Which is true in the push_back/emplace_back case (you need to adjust your call).

GC, on the other hand, is magic pixie dust, if my definition is correct ;)

replies(1): >>26341591 #
11. MaxBarraclough ◴[] No.26341591{3}[source]
> I think this is said to express "simply replacing that function call/method/... without changing your calls/methods/coding style is not going to magically solve your problems"

I agree, but you're doing all the work in conjuring this interpretation. Saying it's not magic doesn't say anything substantial.

If it were possible to mechanically replace all references to push_back by emplace_back for an easy performance boost, then we wouldn't need emplace_back to be its own member function, it would just be a compiler optimisation. You have to make shallow and localised changes to make proper use of emplace_back, but isn't that the best we can hope for? Far easier than parallelism, say, where sweeping architectural changes may be necessary.

12. Koshkin ◴[] No.26341620[source]
> auto w = Widget(1,2,3);

Note that unlike in Java or C# this is not the right way to initialize a variable in C++.

replies(4): >>26341854 #>>26342638 #>>26342704 #>>26344117 #
13. MaxBarraclough ◴[] No.26341854{3}[source]
Great point, I'd missed that! It should be:

    Widget w(1,2,3);
14. SloopJon ◴[] No.26342083[source]
> It also states that it's more work for the compiler, stating that the template resolution seems more complex, and suggesting it's likely to bloat compile times. That's a more persuasive point (hard numbers are provided at the end of the article)

I take the author's point about the cost of compiling a template, but the benchmark is odd: a thousand functions with a single call to emplace_back(). C++ programmers are (in)famously willing to trade compile-time performance for run-time performance. Complement the compile benchmark with a realistic program benchmark. In a typical program, I would expect emplace_back() to be called many more times than it's compiled.

replies(1): >>26342408 #
15. hules ◴[] No.26342408{3}[source]
Well I'm willing to trade compile-time performance for run-time performance on parts where it does matter. But these parts are quite rare, most of the code of an application is not a performance hot spot where using emplace_back instead of push_back will make a measurable performance difference. So I think the author about compile time is interesting.
16. xuhu ◴[] No.26342447[source]
When adding items to maps it becomes less obvious (when learning from examples at least) whether map.emplace({Key(args), Value(args)}) avoids copies for Key, Value, pair<Key, Value>, or all of them.
17. xdavidliu ◴[] No.26342638{3}[source]
Herb Sutter offers another viewpoint:

In particular: #5 on that page: "we see that the right-hand style is not only more robust and maintainable for the reasons already given, but also arguably cleaner and more regular with the type consistently on the right when it is mentioned"

[1] https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style...

replies(1): >>26344710 #
18. pragmatic8 ◴[] No.26342704{3}[source]
Well, that piece of code is a perfectly valid way to initialize a variable in C++.
19. shawnz ◴[] No.26343133[source]
Wouldn't that have changed the semantics (since you're not calling the copy constructor anymore?)

The student accidentally changed the semantics by adding the extra call to the copy contstructor, and therefore Clang didn't know anymore that it could be removed

replies(2): >>26344237 #>>26344288 #
20. nec4b ◴[] No.26344117{3}[source]
It is a completely valid way of initializing a variable in modern C++. Modern compilers will not create a copy assignment, if that is what you are afraid of.
replies(1): >>26344771 #
21. CJefferson ◴[] No.26344237{3}[source]
In that case, push_back to emplace_back changed the semantics (as construct + push_back made a move construction, whereas emplace_back did not).
replies(1): >>26344319 #
22. oleganza ◴[] No.26344288{3}[source]
The warning was not explicit enough to ask you to remove the constructor as well as changing the method name. If it called for "hey, you can remove the constructor and emplace_back would automagically call it for you in place!" and then checked that the constructor is not spelled out there, then there would not be such issue and the tool would be 100% helpful.
replies(1): >>26344327 #
23. shawnz ◴[] No.26344319{4}[source]
Good point, it doesn't seem to be any bigger of a change than the original suggestion. Perhaps they mistakenly thought nobody would call emplace_back in that way by accident.
24. shawnz ◴[] No.26344327{4}[source]
It did ask to remove the constructor as well as changing the method (note which parts are covered by tildes).

My question is, after having incorrectly only done one of those things, how could it have known that the user was only halfway though a two-part operation and didn't actually mean for it to be that way? Clang doesn't know how the code was in the past.

25. MaxBarraclough ◴[] No.26344710{4}[source]
I don't think Herb's points apply to an ordinary declare-an-object-and-immediately-initialize-using-the-constructor, do they?

He gives an example using aggregate initialization, but that's not the same thing:

    // Classic C++ declaration order     // Modern C++ style

    employee e{ empid };                 auto e = employee{ empid };
    widget w{ 12, 34 };                  auto w = widget{ 12, 34 };
He makes the case that it's nicer to use auto pretty much wherever we can, to avoid repeating ourselves regarding type, and to avoid unexpected conversions. The auto keyword doesn't apply in our case, there's no way to use auto to simplify a statement like:

    Widget w(1,2,3);
replies(2): >>26344839 #>>26346405 #
26. MaxBarraclough ◴[] No.26344771{4}[source]
> Modern compilers will not create a copy assignment, if that is what you are afraid of.

Probably, but using the canonical syntax removes all doubt, following the C++ language specification. That's a small but real disadvantage in robustness when using the unnecessary assignment, and I'm not aware of any reason to favour using it.

replies(1): >>26346833 #
27. Koshkin ◴[] No.26344839{5}[source]
Exactly. auto is only useful in contexts where the type is expected to be inferred (by the compiler).
28. SloopJon ◴[] No.26346405{5}[source]
Actually, he does discuss this in section 4b. He doesn't come out as strongly in favor of it, "The jury is still out on whether to recommend this one wholesale," although he does list what he considers some advantages. He also acknowledges in section 6 that it doesn't work for non-moveable types.

Despite my tendencies towards almost-always auto, I can't say that I consistently use auto x = type{init}, but there is certainly nothing wrong with it.

replies(1): >>26347292 #
29. eklitzke ◴[] No.26346833{5}[source]
Copy elision in this example is also required by the language specification (if you're using a modern version of C++).
replies(1): >>26349804 #
30. gpderetta ◴[] No.26347292{6}[source]
> He also acknowledges in section 6 that it doesn't work for non-moveable types.

Actually these days it does. Copy/Move elision is mandated by the language in this case and no valid copy/move constructor is required. Similarly is no possible to return non-movable non-copyable types from functions which opens the possibility of interesting code patternes.

31. MaxBarraclough ◴[] No.26349804{6}[source]
Neat, I didn't know that.

I still don't like it though. I'd rather express what I want to happen than express something I don't want to happen in the knowledge that the standard requires my compiler to be sufficiently smart to repair my statement. Consistent behaviour across different language versions is another plus.

If you'll permit me a moment's snark:

Most OOP languages: You can't copy objects. You may copy references to objects. Copying objects is one road to madness. Move semantics is another.

Old C++: You can copy objects, and specify your own copy constructors (which are permitted to side-effect), but the compiler is permitted to optimise away copy operations under certain circumstances. You shouldn't assume your copy constructor will be invoked whenever you see what looks like a copy.

New C++: You can copy objects, and specify your own copy constructors (which are permitted to side-effect), but the compiler is permitted to optimise away copy operations under certain circumstances, and beyond that, is now required to optimise away copy operations in some specific circumstances. You shouldn't assume your copy constructor will be invoked whenever you see what looks like a copy. When you see a copy operation in certain specific contexts, you are permitted to assume your copy constructor will not be invoked.

As with so many discussions of C++, I'm reminded of two quotes:

> Within C++, there is a much smaller and cleaner language struggling to get out.

- Bjarne Stroustrup [0]

> Do you know how the Orcs first came into being? They were elves once, taken by the dark powers, tortured and mutilated. A ruined and terrible form of life. And now... perfected.

- Saruman

[0] https://en.wikiquote.org/wiki/Bjarne_Stroustrup