@@landondyer I had to learn C++ programming back in 2002 - 2003 for my 3rd year University subject called OOP and data structures. After I had passed that exam I didn't really use C++ for about 10 years. Picked it up again sometime in 2014-2015 when I decided I needed to dive deeper into the gritty - nitty details of C++'s language semantics, syntax rules and the C++ memory model. Have been using this wonderful powerful programming language ever since. I really love all the modern C++ language features especially those that were introduced with the first 3 modern C++ language standards (C++11, C++14 and C++17). At the moment I'm in the process of getting acquainted with all the new C++ language and library features that were introduced with the C++ 20 standard.
@@atib1980 we did OOP in my second year as 1st year Intro to Programming switched to Java - our learning materials were still in pre-print. Like you, after uni I left C/C++ behind - but I had been doing C/C++/ASM since I was 15. I’m shocked that the language, although “modernised”, still looks absolutely horrible to read and requires several scans of lines, parameters, modifiers, syntaxes and so on to properly figure out what code is doing. Not looks like it is doing but is actually doing. My SAAS backend is in C++ interfacing with a SPA Frontend. For the sanity of myself and others, functionality at the back relies heavily on an in-house framework to standardise everything - die hard C++ers will use and abuse everything available if you let them and render the codebase horrible for consumption by others that know it pays to keep things simple 🙂 I do love the performance I get at runtime though - my end to end round trips are lightening fast allowing the Frontend to operate extremely responsively and seamlessly 👍 One thing I had to sort out straight away was dependency management and build workflow. Ended up throwing away makefiles and using Maven which has been ok so far. “mvn clean package” gives me shareable libraries and executable binaries for AMD64, AARCH64, Linux and Windows with compile-time Native ARchive dependencies pulled in from a Nexus repo 🙏
As someone who was in the group of not knowing what move semantics are, I now feel like I understand them! Essentially, skip the copy constructor call, skip the temporary object creation and simply call the move constructor for increased performance. Set the old pointer to nullptr in the move constructor.
According to core guidelines C.65 in the example of Widget's move assignment operator he should have added a simple check for self-reference, it would solve problem pointed out by a woman from the audience.
[46:15] "You don't really have to protect against move to self" Well, unless you're holding a pointer to a Widget. Then if you do w = std::move(*w.pw), you end up deleting the thing you're trying to move from, which is not exactly self-assignment but still dangerous. Arthur O'Dwyer explains this situation in his talk about RAII, also from CppCon 2019, and presents it as a justification for preferring the std::swap method.
so many good things about this presentation (audio, voice, articulation, rhythm etc), it breaks my heart to say that without seeing how exactly the statements are compiled and how exactly the memory is managed under the hood, i can't follow it.
I find tutorials on this topic very lacking. First time ever hearing about noexcept for example. Or the move constructors, where you have to call move anyways. I wonder if it's possible for standard to implement automatical moves where you don't need copy (passing temporary variables through series of const ref parameters in nested function calls for example).
But what if you had Widget as a data member and in the move assignment/copy, you do this->widget = std::move(Widget&& w);. Wouldnt this cause some infinite loop? In that case would i just have to do this->widget = w.widget and have no other choice but to use copy constructor?
Do we really need std::move(w.pi); ? pi=w.pi; should what we require ? eventually pi=std::move(w.pi) will do the same as its just a static cast and its relevant for class objects so that their move operators are called but not relevant for pointers. please correct me if wrong. delete pi; isn't incorrect? it should be delete[] pi;? assuming you used int* to allocate dynamic array?
@18:34, private var i, s, or pi. The default move-constructor or default assign move-operator, what will happens to the previous var/fields are they being reset like this? w.i = 0, w.s = NULL, w.pi = nullptr. Is this right?
I usually prefer to call the destructor explicitly when implementing move assignment operators. That way, I avoid duplication of the resource cleanup code. However, this doesn't seem to be common practice. Are there any arguments (besides performance) against calling the destructor in move assignment operators?
According to the standard, the lifetime of an object ends when its destructor is called, and use after lifetime is UB. Roughly, incurring this kind of UB may lead to the compiler breaking your program. If you manually invoke the destructor for any object, you must construct a new object in that memory before you can re-use it; if that object is a stack object, you must do so using placement new, and you must do so before the destructor is automatically invoked (this is mandatory, the destructor must not be called after the end of the object's lifetime). If a move assignment operator manually invokes the destructor, it must immediately construct a new object at the this pointer using placement new.
@Ted Thanks a lot for the explanation! Do you then agree that the following could serve as a canonical move-assignment implementation, disregarding performance and assuming that a noexcept move-constructor is available? TYPE& operator = (TYPE&& other) noexcept { TYPE::~TYPE(); new(this) TYPE(std::move(other)); return *this; }
I've now moved an updated version of this question to stackoverflow.com/questions/58280104/canonical-c-assignment-operators that incorporates the feedback from @Ted.
While the deleted pointer is indeed moved to itself, it also gets set to nullptr in the moved-from object at the end, leaving nothing behind. One advantage of std::swap over explicit nulling is that you get to keep the allocation in this case.
@@D0Samp How does that answer the question? I was also wondering about a dangling pointer. If we assign to self, and delete the pointer in the beginning of the move assignment operator, then essentially the object that we're moving from (ourselves) would also have a pointer that points to nowhere, which we would try to move to our object, resulting in a dangling pointer. How does std::swap help here?
With the move assignment from the video, we will end up having a nullptr which is not transferring ownership at all. I think the presenter didn't understand the question that the woman asked properly.
I have a quick question. What happens if the previous variable is now empty? Even though it is empty, it retains a specific size and cannot become an external resource to other processes until it is no longer in scope.
If we have dynamic memory, we can manually free it or reuse it for other purposes. However, in RAI Initialization, it will be freed as soon as the scope is finished.
Around minute 45, about the move to self problem. Using std::swap() wouldn't make it safe and the object invariant since the pointer wouldn't be deleted?
By unsafe you probably mean that the original value in *pi is lost, and therefore you would prefer the swap implementation. I think Klaus's argument is that if you write w = std::move(w), then you are expressing the idea that you no longer care about the value in w, therefore it is okay to release the memory in pi and reset it to nullptr. Either approach is fine and he indeed say you need to make a decision based on what you want. He also points out that one caveat of the swap implementation is that the memory of the assigned to object is not immediately released, which I think is an important point to note because you are relying on a properly written constructor to do the work.
@19:23 does anyone know what he meant when he said "as soon as you leave out any reference, it would be the copy constructor or the copy assignment operator"?
At 45:32, can it be that the passed in object (w)'s destructor gets called while you are move-ing its fields and potentially move garbage to *this object?
Not under normal conditions, since the object will not go out of scope as long as the move assignment operator holds a reference to it. An rvalue&& reference behaves the same as a lvalue& reference in that regard.
Hey just wanted to add that the delete pi around the 40:00 minute mark would likely be a delete[ ] pi in practice because the pi was likely created with a new [ ].
It would make sense only if both classes were of the same type. But what if you know only a common base class? It is either unsafe, or you need a dynamic cast with exception when dynamic cast fails. It is a troublesome situation no matter how you look at it.
std:move() takes a forwarding reference, not an rvalue reference. Please see the second part of your talk, which covers forwarding references in detail.
I have to say, that when a language needs an hour long two part video on basic functionality such as move, there is something fundamentally wrong with it.
It’s an absolutely horrible language - there is no doubt about that 🙂 I’ve just mandated it as the primary development language at my startup, side-stepping go, rust, zig, carbon et al, so I must be absolutely horrible too 😂 20+ years doing Java which has become horrible in different ways led me to take another look at C/C++… it’s a completely different beast to when I was doing game and graphics engines in Borland, Watcom, TASM and MASM back in the day. Windows API, MFC and COM programming using Hungarian syntax all of a sudden doesn’t seem that bad at all compared to this b.s. they are actually calling “modern”. A modern nightmare with jobs that don’t pay anywhere near enough to justify the cognitive (over)load and management/maintenance headaches. I’ve had 5 segfaults due to bad inputs this week, the last time I had a program crash out at runtime this way was 1999 🤷♂️ Hello Again C++ World 💩
well, what do you think why nobody uses JS for let’s say commercial embedded development? Some languages need to be hard, in order to endow the programmer with superpowers. But as it was said, with great power comes a greater responsibility
Esra, I've seen you comments on other channels and I totally agree with you. The language has totally jumped the shark and gets worse with every standards release. I have a book that's over 200 pages that goes over how to use move semantics - just move semantics, that's all the book is about. I'm sorry but that's just absurd. I dunno maybe the language is this way so that more books on how to navigate the mess get sold.
Man i really love these back to basics series talks (specially this one and the RAII by Arthur). Helps me a lot as C++ beginner
I've been using C++ since the 1980s, and it still helps me :-)
This one? ruclips.net/video/7Qgd9B1KuMQ/видео.html
@@landondyer I had to learn C++ programming back in 2002 - 2003 for my 3rd year University subject called OOP and data structures. After I had passed that exam I didn't really use C++ for about 10 years. Picked it up again sometime in 2014-2015 when I decided I needed to dive deeper into the gritty - nitty details of C++'s language semantics, syntax rules and the C++ memory model. Have been using this wonderful powerful programming language ever since. I really love all the modern C++ language features especially those that were introduced with the first 3 modern C++ language standards (C++11, C++14 and C++17). At the moment I'm in the process of getting acquainted with all the new C++ language and library features that were introduced with the C++ 20 standard.
@@atib1980 we did OOP in my second year as 1st year Intro to Programming switched to Java - our learning materials were still in pre-print. Like you, after uni I left C/C++ behind - but I had been doing C/C++/ASM since I was 15.
I’m shocked that the language, although “modernised”, still looks absolutely horrible to read and requires several scans of lines, parameters, modifiers, syntaxes and so on to properly figure out what code is doing. Not looks like it is doing but is actually doing.
My SAAS backend is in C++ interfacing with a SPA Frontend. For the sanity of myself and others, functionality at the back relies heavily on an in-house framework to standardise everything - die hard C++ers will use and abuse everything available if you let them and render the codebase horrible for consumption by others that know it pays to keep things simple 🙂
I do love the performance I get at runtime though - my end to end round trips are lightening fast allowing the Frontend to operate extremely responsively and seamlessly 👍
One thing I had to sort out straight away was dependency management and build workflow. Ended up throwing away makefiles and using Maven which has been ok so far.
“mvn clean package” gives me shareable libraries and executable binaries for AMD64, AARCH64, Linux and Windows with compile-time Native ARchive dependencies pulled in from a Nexus repo 🙏
He is truly a legend and one of my role models in the C++ world! Thanks CppCon for 'back to basics' idea and for uploading videos here for free!
Best move semantics video I've ever seen!
As someone who was in the group of not knowing what move semantics are, I now feel like I understand them! Essentially, skip the copy constructor call, skip the temporary object creation and simply call the move constructor for increased performance. Set the old pointer to nullptr in the move constructor.
this talk is actually very good explanation about weird part of move semantics
Small correction - @53:16 He mentions Core Guideline C.15. There is no C.15 (yet) - he meant F.15.
Excellent talk, I came to learn new things which help me improve my own project! Thanks :)
Great to hear!
I'm so sad that Klaus didn't mention that you can do std::move(w).i and std::move(w).s. The members of an rvalue are rvalues!
Wouldn't i(w.i) be not a move since int is a trivially copyable type, and move on such types is a no-op?
Another reason to avoid raw pointers? I think so.
According to core guidelines C.65 in the example of Widget's move assignment operator he should have added a simple check for self-reference, it would solve problem pointed out by a woman from the audience.
What does it mean by non owning pointer?
The move operator is actually an eviction operator. :)
I'm taking your house.
Seems more of a burglar operator to me.
I'm taking everything from your house, you're welcome.
It is just a title transfer instead of building the same house.
It's more like "I am transforming my house into a caravan so that it can move". The std::move function is essentially a static_cast.
[46:15] "You don't really have to protect against move to self"
Well, unless you're holding a pointer to a Widget. Then if you do w = std::move(*w.pw), you end up deleting the thing you're trying to move from, which is not exactly self-assignment but still dangerous. Arthur O'Dwyer explains this situation in his talk about RAII, also from CppCon 2019, and presents it as a justification for preferring the std::swap method.
Thank you for using the uniform initialization.
...but not in the member initializer list :-(
The best explanation ever!
so many good things about this presentation (audio, voice, articulation, rhythm etc), it breaks my heart to say that without seeing how exactly the statements are compiled and how exactly the memory is managed under the hood, i can't follow it.
Showing that would probably have left behind a good part of the audience...
Thank you, excellent talk, very easy to understand.
I find tutorials on this topic very lacking. First time ever hearing about noexcept for example. Or the move constructors, where you have to call move anyways.
I wonder if it's possible for standard to implement automatical moves where you don't need copy (passing temporary variables through series of const ref parameters in nested function calls for example).
But what if you had Widget as a data member and in the move assignment/copy, you do this->widget = std::move(Widget&& w);. Wouldnt this cause some infinite loop? In that case would i just have to do this->widget = w.widget and have no other choice but to use copy constructor?
Great talk! Cleared up details for me, thanks
excellent talk!
Do we really need std::move(w.pi); ? pi=w.pi; should what we require ? eventually pi=std::move(w.pi) will do the same as its just a static cast and its relevant for class objects so that their move operators are called but not relevant for pointers. please correct me if wrong.
delete pi; isn't incorrect? it should be delete[] pi;? assuming you used int* to allocate dynamic array?
Great talk !!! very clearned up details for me.
@18:34, private var i, s, or pi. The default move-constructor or default assign move-operator, what will happens to the previous var/fields are they being reset like this? w.i = 0, w.s = NULL, w.pi = nullptr. Is this right?
Super awesome Video! Thank you for sharing! Vielen Dank :)
53:21 that should be F.15 not C.15
one question I have is, how does a chained move assignment works?
Excellent talk!
I usually prefer to call the destructor explicitly when implementing move assignment operators. That way, I avoid duplication of the resource cleanup code. However, this doesn't seem to be common practice. Are there any arguments (besides performance) against calling the destructor in move assignment operators?
According to the standard, the lifetime of an object ends when its destructor is called, and use after lifetime is UB. Roughly, incurring this kind of UB may lead to the compiler breaking your program. If you manually invoke the destructor for any object, you must construct a new object in that memory before you can re-use it; if that object is a stack object, you must do so using placement new, and you must do so before the destructor is automatically invoked (this is mandatory, the destructor must not be called after the end of the object's lifetime). If a move assignment operator manually invokes the destructor, it must immediately construct a new object at the this pointer using placement new.
@Ted Thanks a lot for the explanation! Do you then agree that the following could serve as a canonical move-assignment implementation, disregarding performance and assuming that a noexcept move-constructor is available?
TYPE& operator = (TYPE&& other) noexcept {
TYPE::~TYPE();
new(this) TYPE(std::move(other));
return *this;
}
I've now moved an updated version of this question to stackoverflow.com/questions/58280104/canonical-c-assignment-operators that incorporates the feedback from @Ted.
Wouldn't the code of self-move assignment operator result in a dangling pointer?
While the deleted pointer is indeed moved to itself, it also gets set to nullptr in the moved-from object at the end, leaving nothing behind.
One advantage of std::swap over explicit nulling is that you get to keep the allocation in this case.
@@D0Samp How does that answer the question? I was also wondering about a dangling pointer. If we assign to self, and delete the pointer in the beginning of the move assignment operator, then essentially the object that we're moving from (ourselves) would also have a pointer that points to nowhere, which we would try to move to our object, resulting in a dangling pointer. How does std::swap help here?
With the move assignment from the video, we will end up having a nullptr which is not transferring ownership at all. I think the presenter didn't understand the question that the woman asked properly.
illuminating
Great presentation!
Part 2 is here: ruclips.net/video/pIzaZbKUw2s/видео.html
I have a quick question. What happens if the previous variable is now empty? Even though it is empty, it retains a specific size and cannot become an external resource to other processes until it is no longer in scope.
If we have dynamic memory, we can manually free it or reuse it for other purposes. However, in RAI Initialization, it will be freed as soon as the scope is finished.
at 28:10, does he mean "If I do not make *move* constr noexpcet" ? he said "copy constr".
By the way, how can I find the implementation of std::move like you did?
Around minute 45, about the move to self problem. Using std::swap() wouldn't make it safe and the object invariant since the pointer wouldn't be deleted?
By unsafe you probably mean that the original value in *pi is lost, and therefore you would prefer the swap implementation. I think Klaus's argument is that if you write w = std::move(w), then you are expressing the idea that you no longer care about the value in w, therefore it is okay to release the memory in pi and reset it to nullptr. Either approach is fine and he indeed say you need to make a decision based on what you want. He also points out that one caveat of the swap implementation is that the memory of the assigned to object is not immediately released, which I think is an important point to note because you are relying on a properly written constructor to do the work.
@19:23 does anyone know what he meant when he said "as soon as you leave out any reference, it would be the copy constructor or the copy assignment operator"?
If you leave an ampersand than it would call the copy constructor instead of move
wow great video. and where can I get information about the coding guidelines which you were referring about?
Maybe cppcoreguidelines??
At 45:32, can it be that the passed in object (w)'s destructor gets called while you
are move-ing its fields and potentially move garbage to *this object?
Not under normal conditions, since the object will not go out of scope as long as the move assignment operator holds a reference to it. An rvalue&& reference behaves the same as a lvalue& reference in that regard.
Hey just wanted to add that the delete pi around the 40:00 minute mark would likely be a delete[ ] pi in practice because the pi was likely created with a new [ ].
@51:47 - Why is there no point in move operations for virtual classes?
It would make sense only if both classes were of the same type.
But what if you know only a common base class? It is either unsafe, or you need a dynamic cast with exception when dynamic cast fails. It is a troublesome situation no matter how you look at it.
I don't get it, at which point s + s = s is valid ?
53:30
why std::array is 'expensive' to move???
because the only way to 'move' it is to copy it, it's a value type.
~ 16:20 Why does move take an rvalue reference "T&& t" if it is supposed to return an rvalue reference ?
that's a universal reference which can bind to anything. check out Scott Meyers' book and talks..
std:move() takes a forwarding reference, not an rvalue reference. Please see the second part of your talk, which covers forwarding references in detail.
I have to say, that when a language needs an hour long two part video on basic functionality such as move, there is something fundamentally wrong with it.
C++ is many languages in one, including C. The talk covers that - it can’t just presume that you will only use the modern parts and ignore all else.
Is it that basic though?
It’s an absolutely horrible language - there is no doubt about that 🙂
I’ve just mandated it as the primary development language at my startup, side-stepping go, rust, zig, carbon et al, so I must be absolutely horrible too 😂
20+ years doing Java which has become horrible in different ways led me to take another look at C/C++… it’s a completely different beast to when I was doing game and graphics engines in Borland, Watcom, TASM and MASM back in the day.
Windows API, MFC and COM programming using Hungarian syntax all of a sudden doesn’t seem that bad at all compared to this b.s. they are actually calling “modern”.
A modern nightmare with jobs that don’t pay anywhere near enough to justify the cognitive (over)load and management/maintenance headaches.
I’ve had 5 segfaults due to bad inputs this week, the last time I had a program crash out at runtime this way was 1999 🤷♂️
Hello Again C++ World 💩
well, what do you think why nobody uses JS for let’s say commercial embedded development? Some languages need to be hard, in order to endow the programmer with superpowers. But as it was said, with great power comes a greater responsibility
Esra, I've seen you comments on other channels and I totally agree with you. The language has totally jumped the shark and gets worse with every standards release. I have a book that's over 200 pages that goes over how to use move semantics - just move semantics, that's all the book is about. I'm sorry but that's just absurd. I dunno maybe the language is this way so that more books on how to navigate the mess get sold.
i find this video hard to understand. a video by "the cherno" named "Move Semantics in C++" really kills it. it's down to earth
Yes, The Cherno is brilliant. All his C++ stuff is a god send.
Its not hard 🤣