CppCon 2017: Nicolai Josuttis “The Nightmare of Move Semantics for Trivial Classes”

preview_player
Показать описание


Assume, we implement a very simple class having just multiple string members. Even ordinary application programmer prefer to make it simple and fast.

You think you know how to do it? Well beware! It can become a lot harder than you initially might assume.

So, let’s look at a trivial class with multiple string members and use live coding to see the effect using different implementation approaches (using constructors passing by value, by reference, by perfect forwarding, or doing more sophisticated tricks).

Sooner than later we will fall into the deep darkness of universal/forwarding references, enable_if, type traits, and concepts.

Nicolai Josuttis: IT Communication



*-----*
*-----*
Рекомендации по теме
Комментарии
Автор

Well, I would argue for the pass-by-value-then-move option: It expresses the idea fairly simply, while giving the compiler everything it needs to optimize away the overhead. "Everything should be made as simple as possible, but not simpler."

ChronosSk
Автор

That's a very good presentation after all. It summarizes many things regarding pass-by-value and pass-by-reference techniques, and clearly describes the mentioned problems. I like it.

canberksonmez
Автор

Question: How many mallocs does X require.
C++ programmer: Four! No, two! No, six! Wait... it depends.
C programmer: How many do you want?

sprytnychomik
Автор

I can always count on Nicolai to present relatively new C++ constructs in a clear, concise, and idiomatic fashion

bobbymah
Автор

Some good information, but I fell like the "Haha, isn't C++ awful?" stuff is uncalled for when the example code is contrived in such a way to emphasize edge/expert cases. The solution at 13:25 (pass by value and use std::move) is simple, straightforward, and provides the optimal performance the talk is supposedly aiming at; everything else is just a tangent about argument forwarding (passing the `const char*` into `std::string` without creating a temporary), and non-explicit constructors with default arguments (the issues with `Cust c = ...;`). Since it's never presented as such though, it makes it look like this really straightforward thing (using move semantics to improve performance) is a huge mess because these other interface issues are being thrown on top of that.

Worse still though, even those things thrown into the mix, the straightforward solution at 13:25 _still works perfectly_ for the example given: It creates a special case for the one-argument const char* constructor (which is self-inflicted by the issues above), but it works, is easy to read/write, and is super straightforward. Going into templates is just overkill for its own sake without even an acknowledgement that no, this is completely unnecessary, and requiring this sort of thing in a real-world environment _probably_ indicates something wrong with your design.

Victarus
Автор

Consider skipping to 17:00 if the earlier stuff seems too repetitive.

tohopes
Автор

You cannot move your template constructors to other compilation unit, so you let everything visible by compiler so it can use optimizations. If compiler can see everything and use optimizations then just use pass-by-value. But if you want to move your constructors to other compilation unit and maybe even use pimpl idiom for ABI compatibility then for best performance you have to write multiple constructors.

LordNezghul
Автор

The person from the audience at 48:43 pointed out the only intelligent solution in this whole presentation - make members public and use aggregate initialization. That will do the right thing and generate optimal code in all cases while saving you from the forwarding constructor insanity. I honestly don't understand the sarcastic reaction from the presenter.

martinkutny
Автор

There is a mistake in the slides at 46:10 that is the number of moves in the second version, which should be 7 moves instead of 5 moves. If you don't believe me, then go back to the slides at 15:45 and count the number of moves yourself.

deltarambo
Автор

Correct me if I'm wrong - it doesn't matter how you receive it, by const reference or by const pointer, the initialization first(f) and last(l) will create copy. So eventually we have 4 mallocs, not 2
char* c = new char[5]{};
c[0] = '1';
c[1] = '2';
c[2] = '3';
c[3] = '4';
std::string a(c); // copying
delete c;
std::cout << a; // 1234

mand
Автор

When he said that the arrayness of the arguments didn't decay I went "Oh _god_."

Because that means every length of string literal bloats the binary for no good reason. If there is any nontrivial action in your perfect forwarding constructor...

NXTangl
Автор

"And the rest of you? Do you hate C++?" Well, after watching the horrible mess just before that, kinda.

Автор

23:15 By compiler's vote, gcc and clang are right. ;-)

michal.gawron
Автор

Microsoft is correct here. It should be const char *& not const char *, because you make a non-universal reference i.e. a rvalue reference to a const char *&& (non-forwarding)

ryannicholl
Автор

Why should initializing Cust (Base) with VIP (Derived) be valid? If VIP had any additional data members, wouldn't they be sliced? Shouldn't it just be: Derived is a Base, but Base isn't Derived??

jankodedic
Автор

I hope C++20 will have a cool feature that a programmer can say hey compiler give me a lollipop.

mcbernnard
Автор

I'd use pass by value and move (which should really be implicit and guaranteed where it is possible and efficient [ie: for common cases such as in constructors]) and avoid defaulted arguments.
In fact google strictly forbids it. I think efficient code should be idiomatic and simple.
If it's not then it's a bug in the language. And I'm not coding around a language, it's too painful.

LemonChieff
Автор

Any good engineer will trade off clarity and simplicity against performance. There may be times when performance supercedes clarity and simplicity, but in practice, that's not very often. the language must allow for the latter case, but the programmer is responsible to make the trade-off appropriately for the use case.

I enjoyed this talk.

homomorphic
Автор

On my machine, using MSVS 19.1030324, adding the line "using customer::customer" makes "VIP v{"Boss"};" fail to compile, citing ambiguous constructor.

ColorfirePluma
Автор

36:30 I cannot get why does VIP v2{v1} work when we used !std::is_same_v for SFINAE. VIP is not the same as Cust and we should face with the same problem with the copy constructor as before, shouldn't we?

samolisov