Classes part 10 - Rule of Five - Have fun reducing memory allocations! | Modern Cpp Series
Vložit
- čas přidán 18. 05. 2024
- ►Full C++ Series Playlist: • The C++ Programming La...
►Find full courses on: courses.mshah.io/
►Join as Member to Support the channel: / @mikeshah
►Lesson Description: In this lesson I discuss the rule of five (otherwise known as the law of 5 or the big 5). I first will recap a simple class data structure recapping the big 3 (constructor, destructor, and copy constructor), and showing where these special member functions are invoked. Then I will remind you of the idea of move semantics (i.e. the transfer of resources from one object to another). When used appropriately, move semantics can save significantly on performance by avoiding copies. Towards the end of the lesson, I will show you how copies are often made, and sometimes unknowingly (like in the case of a vector when it is getting resized as you add elements), and how move semantics can help significantly improve performance by reducing allocations.
►CZcams Channel: / mikeshah
►Please like and subscribe to help the channel! - Věda a technologie
Excellent example Mike. Neat, plain, simple, fast and complete. I would really appreciate if you can post a similar video showing all the possible ways to build a vector (or any other STL container for that matter) of objects in C++ highlighting the best practice to do so. You can create vectors of objects from the stack or from the heap, you can use move semantics, you can use objects themselves or pointers to them, etc … Many possibilities here, but what is the best way of doing things ? Thanks for these very good and informative videos!
Thank you for the kind words! Coming up in the series will eventually be talking about STL and these general data structures available. Then I've noted your request, as at some point this is the kind of video that could go into a Data Structures Playlist for C++.
After watching this video, I really should familiarize myself with the std::move!!
Wonderful series
Been on a binge watch since yesterday
Cheers! Thanks Nishant!
Wonderfully explained as always. Thank You.
Cheers, thank you for the kind words!
Great explanation!
Cheers!
very useful, thanks Mike
Cheers!
This cleared up a confusion I had about the move operation. It didn't make sense to me, why would we reset variables of the source if the destination was claiming its memory, it seemed as if resetting source was resetting destination. But then I see, it's only for heap allocated memory that move operations truly moves, whereas stack variables are copied. Thanks for the video.
Cheers!
You should also declare the move constructors noexcept so that when using the struct in STL containers it will not default to copying
Correct! I haven't covered noexcept yet in this series. It's probably the right default in C++ to be honest
@@MikeShah Yeah just wanted to put that comment in there so anyone who watches this vid knows to do so. I am loving the series and learning some great things, thank you for making these
@@rdd-technical6824 Cheers and absolutely -- I'm sure that's going to help future folks out :)
Hi Mike, why you just assigned data directly in move constructor instead of creating a new memory and copying content inside it. To me it seems we are doing shallow copy here. And due to that only valgrind reported less memory allocations.
@MikeShah I have the same doubt. Please comment on it
Excellent example!!!! 46 lessons have been completed, 87 lessons left. *FIGHTING
Cheers! Well done!
very excellent lecture. Thanks Mike.
In the move assignment operator implementation, should we be freeing the allocated memory by calling `delete[] m_data` before assigning m_data to source.m_data?
In the copy assignment operator, we freed the memory using delete[] m_data. Shouldn't we follow the same practice in the move assignment operator?
Should not need to free the data, we are just re-assigning the pointer -- move assignment thus is faster, as it's taking over ownership of the data without having to reallocate. Old m_data then points to nothing (i.e. no 'sharing' or 'moving' of data -- it is effectively empty or nullptr)
@@MikeShah, if move assignment is invoked, valgrind will show memory leaks. Before repointing, the existing memory needs to freed for assignment. Your example only used move constructor. Also, better to use std::move on the string member m_name and no need to assign it to "" afterwards either.
Correct, do need to eventually free the data, just not after 'moving' it (so still need to do so in the destructor of whoever owns the memory)@@mingxue6327
I'm very new to C++ mostly with Java knowledge, I wonder why would one chose to define unimplemented constructors, I understand the point of interfaces but what's the need for a constructor with no implementation?
Could you please make a video on how to call a WebAPI in C++
Will add to the wishlist!
Since the move constructor transfers ownership and the original allocated object is being nullified, wouldn't it be even more efficient, in terms of size of allocated memory, to delete the original afterwards?
One thing to keep in mind is that 'moving' is not equivalent to 'destroying' an object. The nullified object will otherwise be reclaimed by the destructor and save memory in that way. I suppose if there was some way to know exactly how many objects you needed, you would save stack space when allocating those objects to otherwise not allocate them, and just keep the object you need that owns the memory you'll need. Pool allocators for example are designed to optimize for allocatations where you may have a fixed size number of objects, and otherwise can avoid major reallocations by just transferring and 'recyling' (i.e. moving) memory around.
Hey Mike, amazing video! You really helped me understand the rule of 5 better. I am just wondering why you didn't delete the array before giving m_data the new value. Doesn't that lead to a memory leak ?
Cheers! At what timestamp are you referring?
@@MikeShah At 14:42, line 51 in the move assignment operator method.
@@karm0s304 When we 'move assign', we are transferring ownership. So I don't want to destroy the data, but rather only have one pointer to the data. If I delete the data, then the data will be null :) Remember the '=' with a pointer type is simply updating the pointer to the memory. Note also, for the 'std::string' m_name, I am using an '=' sign to transfer ownership, but the move-assignment operator that has implemented in std::string should do the proper 'move-assignment' for us (as opposed to a copy).
@@MikeShah Thanks for your reply ! Actually I am talking about the old data that m_data was holding before giving it the new pointer. If I understood correctly, the move assignment operator method will be called when we try to move an object into another existing object. Let's say I try to move B into A, object A's m_data will have a pointer to an array of 10 elements, so if we move the value of B's m_Data into A's m_Data without deleting the old array that A was holding that would cause a memory leak.
The only place where we could directly do that is in the move constructor because the receiving object didn't allocate any data before.
Ah, yes you're correct. For the move-assignment operator we should delete[] beforehand (because we are the owner in this example)
In the move assignment operator, it makes sense why m_data from the source would be set to nullptr to avoid a double delete error in the destruction of the object. Running through valgrind it shows more allocations than frees when calling a move assignment. But when I switch to using std::move on m_data, valgrind still shows that there is unfreed memory - but now has equal allocations to frees. Any insights into what is happening? Also, would this be considered leaking data, or just the natural consequence of allocating memory just to reassign it in this example?
Interesting -- I wonder if that is a bug? As long as it's showing that all memory is freed that should be okay however (i.e. no leaks detected).
Nice video. I'm a bit confused as to how you would move a unique_ptr?
A unique_ptr itself can be moved (not copied however). So you could do something like: std::unique_ptr object1 = std::move(object2); in your move-assignment constructor. This might provide some insight: docs.microsoft.com/en-us/cpp/cpp/how-to-create-and-use-unique-ptr-instances?view=msvc-170
Hi @MikeShah at 8:30 , can you please explain how you returned result from foo(), as the function foo() has return type of IntArray, BUT at it's call from main(), you have not written any variable to store it, isn't it an error or warning?
If I mark the function as nodiscard, then a warning will be given since the returned value is not used.
@@MikeShah Thank you so much 😁
what is that operator= used for ?
I did not see it get called .
Used for assignment for already created objects to copy
Hi @MikeShah, why you just assigned data directly in move constructor instead of creating a new memory and copying content inside it. To me it seems we are doing shallow copy here. And due to that only valgrind reported less memory allocations.
Move assignment or move constructor is in fact doing a shallow copy -- it's fast! But along with the shallow copy, we are changing ownership, such that the new object has control of the data, and the other object we are moving from, then does not 'own' the memory.
@@MikeShah thanks for your reply Mike. I have one more question can we move construct an object from itself?
Hmm, there is a move constructor, but not sure if it could move from itself while being constructed -- do you have a use case?
@@MikeShah Hi Mike thanks for your reply, I don't have a use case at present.