From 507400051b5abb13c712616dab30b9a98fdca744 Mon Sep 17 00:00:00 2001 From: Marcus Mohr <marcus.mohr@lmu.de> Date: Mon, 30 Jan 2023 17:50:35 +0100 Subject: [PATCH] Add example(s) on move semantics and the STL --- notebooks/14_MoveSemantics+RuleOfSix.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/14_MoveSemantics+RuleOfSix.ipynb b/notebooks/14_MoveSemantics+RuleOfSix.ipynb index 064ce52..3bed6a6 100644 --- a/notebooks/14_MoveSemantics+RuleOfSix.ipynb +++ b/notebooks/14_MoveSemantics+RuleOfSix.ipynb @@ -1 +1 @@ -{"metadata":{"language_info":{"codemirror_mode":"text/x-c++src","file_extension":".cpp","mimetype":"text/x-c++src","name":"c++","version":"17"},"kernelspec":{"name":"xcpp17","display_name":"C++17","language":"C++17"}},"nbformat_minor":5,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Move Semantics","metadata":{},"id":"4c244310-2195-4b50-a923-df0b9ba15f7d"},{"cell_type":"markdown","source":"In order to understand what this is about let us take a look at an example from Rainer Grimm's blog [\"Modernes C++ in der Praxis\"](https://www.linux-magazin.de/ausgaben/2012/12/c-11/):","metadata":{},"id":"92b6cf27-b829-4efd-9395-e38f3a330bb4"},{"cell_type":"code","source":"#include <algorithm>\n#include <iostream>\n#include <vector>\n\nclass BigArrayCopy {\n\npublic:\n\n BigArrayCopy( size_t len ) : len_( len ), data_( new int[ len ] ) {}\n\n BigArrayCopy( const BigArrayCopy& other ) : len_( other.len_ ),\n data_( new int[ other.len_ ] ) {\n std::cout << \"copy construction of \" << other.len_ << \" elements\" << std::endl;\n std::copy( other.data_, other.data_ + len_, data_ );\n }\n\n BigArrayCopy& operator=( const BigArrayCopy& other ) {\n\n std::cout << \"copy assignment of \" << other.len_ << \" elements\" << std::endl;\n\n if( this != &other ) {\n\n delete[] data_;\n len_ = other.len_;\n data_ = new int[ len_ ];\n\n std::copy( other.data_, other.data_ + len_, data_ );\n\n }\n\n return *this;\n }\n\n ~BigArrayCopy() {\n if( data_ != nullptr ) {\n delete[] data_;\n }\n }\nprivate:\n\n size_t len_;\n int* data_;\n\n};","metadata":{},"execution_count":2,"outputs":[],"id":"4ffa9b8d-02be-4919-882c-18556a6ded7d"},{"cell_type":"code","source":"int main() {\n \n std::vector< BigArrayCopy > myVec;\n\n BigArrayCopy bArray( 11111111 );\n BigArrayCopy bArray2( bArray );\n\n myVec.push_back( bArray );\n bArray = BigArrayCopy( 22222222 );\n myVec.push_back( BigArrayCopy( 33333333 ) );\n\n}\n\nmain();","metadata":{},"execution_count":3,"outputs":[{"name":"stdout","text":"copy construction of 11111111 elements\ncopy construction of 11111111 elements\ncopy assignment of 22222222 elements\ncopy construction of 33333333 elements\ncopy construction of 11111111 elements\n","output_type":"stream"}],"id":"dd1e7485-7b8e-4728-a217-df899e07d6d8"},{"cell_type":"markdown","source":"Let us analyse the output. Where do the five copies come from?\n<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>\n_____","metadata":{},"id":"333e1d6f-aab5-4c70-859d-db4dafb02981"},{"cell_type":"markdown","source":"We can change that by adding a **move constructor** and a **move assignment operator** to our class:","metadata":{},"id":"6893f7df-99e0-44e9-a79f-8c6e8acba110"},{"cell_type":"code","source":"#include <algorithm>\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::vector;\n\nclass BigArray {\n\npublic:\n\n BigArray( size_t len ) : len_( len ), data_( new int[ len ] ) {}\n\n BigArray( const BigArray& other ) : len_( other.len_ ),\n data_( new int[ other.len_ ] ) {\n cout << \"copy construction of \" << other.len_ << \" elements\" << endl;\n std::copy( other.data_, other.data_ + len_, data_ );\n }\n\n BigArray& operator=( const BigArray& other ) {\n\n cout << \"copy assignment of \" << other.len_ << \" elements\" << endl;\n\n if( this != &other ) {\n\n delete[] data_;\n len_ = other.len_;\n data_ = new int[ len_ ];\n\n std::copy( other.data_, other.data_ + len_, data_ );\n\n }\n\n return *this;\n }\n\n BigArray( BigArray&& other ) : len_( other.len_ ),\n data_( other.data_ ) {\n cout << \"move construction of \" << other.len_ << \" elements\" << endl;\n other.len_ = 0;\n other.data_ = nullptr;\n }\n\n BigArray& operator=( BigArray&& other ) {\n\n cout << \"move assignment of \" << other.len_ << \" elements\" << endl;\n\n if( this != &other ) {\n\n delete[] data_;\n\n len_ = other.len_;\n data_ = other.data_;\n\n other.len_ = 0;\n other.data_ = nullptr;\n }\n\n return *this;\n }\n\n ~BigArray() {\n if( data_ != nullptr ) {\n delete[] data_;\n }\n }\nprivate:\n\n size_t len_;\n int* data_;\n\n};","metadata":{},"execution_count":1,"outputs":[],"id":"e3784252-4e44-4260-a5a1-d2b92f92e264"},{"cell_type":"code","source":"int main() {\n \n std::vector< BigArray > myVec;\n myVec.reserve(2); // get's rid of the final copy operation, when myVec was reallocated\n\n BigArray bArray( 11111111 );\n BigArray bArray2( bArray );\n\n myVec.push_back( bArray );\n bArray = BigArray( 22222222 );\n myVec.push_back( BigArray( 33333333 ) );\n\n}\n\nmain();","metadata":{},"execution_count":4,"outputs":[{"name":"stdout","text":"copy construction of 11111111 elements\ncopy construction of 11111111 elements\nmove assignment of 22222222 elements\nmove construction of 33333333 elements\n","output_type":"stream"}],"id":"ebbe2374-3d00-4340-98a0-9352ff8ca2cb"},{"cell_type":"markdown","source":"As we can see the assignment and copying of the two temporaries in lines # 10 and 11 now uses our **move semantics**.\n\nBut what is does the **\"&&\"** in the interface of the two methods represent? It is an **r-value reference**.\n","metadata":{},"id":"2fead1ad-cee3-4a0d-ba70-b14066a349b9"},{"cell_type":"markdown","source":"### R-Value References\n\nFirst of all, what are \"r-values\"? Complicated question, short (simplified) answer: stuff that can appear on the right-hand side of an assignment, hence the name (originally). Examples of r-values are:\n\n- temporary objects\n- unnamed objects\n- objects whose address is undeterminable\n\nsuch as\n\n```\nint fourtyTwo = 42;\nstd::string a = std::string( \"rhs is an rvalue\");\nstd::string b = std::string( \"r\" ) + std::string( \"-value\" );\nstd::string c = a + b;\nstd::string d = std::move(b);\n```\n\nThe last line is especially interesting. [std::move](https://en.cppreference.com/w/cpp/utility/move) effectively returns an r-value reference for its argument:\n\n\n> std::move is used to indicate that an object t may be \"moved from\", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t. It is exactly equivalent to a static_cast to an rvalue reference type. ","metadata":{},"id":"a2f2682a-1823-46c4-a444-a23fc192ef17"},{"cell_type":"code","source":"int value = 1;\nint& lRef1 = value;\n// int&& rRef1 = value;\nint&& rRef2 = 1;\nconst int& lRef2 = 1;","metadata":{},"execution_count":7,"outputs":[],"id":"7c569151-43ef-44a8-babc-6928ca17b626"},{"cell_type":"markdown","source":"An r-value can bind to an r-value reference, but also to a constant l-value reference. That's why `BigArrayCopy` worked after all. However, binding to an r-value reference, if possible has **higher precedence**. That's what we need in `BigArray` for the move methods.","metadata":{},"id":"1ad6ad50-160f-49d9-b486-2ac84c1cbdbb"},{"cell_type":"markdown","source":"# Rule of Six / Five / Zero","metadata":{},"id":"aa620485-bd12-4727-9c7f-d07f66d0e62a"},{"cell_type":"markdown","source":"The advantage of *move semantics* is that it helps to avoid unnecessary copy operations in order to increase performance. However, it also means that a simple class now could implement the following *six different construction/destruction/assigment methods*:","metadata":{},"id":"6549163b-b1f8-4d7b-9126-0674f01d0022"},{"cell_type":"code","source":"class simple {\n \n // (1) Default Constructor\n simple() {};\n \n // (2) Destructor\n ~simple(){};\n \n // (3) Copy Constructor\n simple( const simple& other ) {};\n \n // (4) Copy Assigment\n simple& operator= ( const simple& other ) {\n simple* aux = new( simple );\n // copy stuff from other to aux\n return *aux;\n };\n \n // (5) Move Constructor\n simple( const simple&& other ) {};\n \n // (6) Move Assignment\n simple& operator= ( const simple&& other ) {\n simple* aux = new( simple );\n // move stuff from other to aux\n // and potentially set other to 'zero'\n return *aux;\n };\n}","metadata":{},"execution_count":14,"outputs":[],"id":"381417d5-fe96-4249-a509-29fcfdd5615b"},{"cell_type":"markdown","source":"The question is, what happens, if we implement none or only some of these methods? For example we know that a default constructor will always be generated automatically by the compiler, if we do not interdict this by deleting it.\n\nThe C++ Core Guidelines have something to say on this topic:\n\n[C.20: If you can avoid defining default operations, do](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-zero)\n> In this case the compiler will auto-generate them for you. This is commonly known as **\"the rule of zero\"**.\n\n[C.21: If you define or =delete any copy, move, or destructor function, define or =delete them all](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-five)\n> The semantics of copy, move, and destruction are closely related, so if one needs to be declared, the odds are that others need consideration too. This is commonly known as **\"the rule of five\"**.\n\n**The Rule of Six**<br>\nextends C.21 to include also the default constructor. The latter is special and, thus, not always include.\n\n**But why?**<br>\nBecause the six special member functions are closely related and the C++ standard is a little bit unintuitive w.r.t. auto-generation when you start selectively defining only some of them. The following table is attributed to *Howard Hinnant (2014)*:<br><br>\n<img src=\"https://www.heise.de/imgs/18/3/6/8/1/8/4/5/DefaultDelete-6dc0661da2aa1431.png\">\n<br><br>\nNote that **user-defined** does not only mean that you implement the constructor/assignment operator, but also includes deleting it (`=delete`) and even explicitely requesting the default variant (`=default`). For full details see \n[Programmiersprache C++: Rule of Zero, or Six](https://www.heise.de/blog/Programmiersprache-C-Rule-of-Zero-or-Six-7463520.html), from which we also borrow the following *cautionary* example:","metadata":{},"id":"4ef8a01b-806b-43b5-9401-5b266143b9e8"},{"cell_type":"code","source":"%%file problematic.cpp\n\n#include <cstddef>\n\nclass BigArray {\n\npublic:\n BigArray( std::size_t len ): len_( len ), data_( new int[ len ] ) {}\n\n ~BigArray() {\n delete[] data_;\n }\n\nprivate:\n size_t len_;\n int* data_;\n \n};\n\nint main() {\n \n BigArray bigArray1( 1000 );\n \n BigArray bigArray2( 1000 );\n \n bigArray2 = bigArray1;\n\n}","metadata":{"trusted":true},"execution_count":2,"outputs":[{"name":"stdout","text":"Overwriting problematic.cpp\n","output_type":"stream"}],"id":"ebe84f93-58ad-47c0-8ae6-9817fab5b867"},{"cell_type":"code","source":"!g++ problematic.cpp","metadata":{"trusted":true},"execution_count":4,"outputs":[],"id":"e45f50e1-2fe8-4a3b-ba00-05edcc21a62b"},{"cell_type":"markdown","source":"Okay, compilation worked. However, when we execute the program we get this:","metadata":{},"id":"79d80da7-ecbe-45d9-9fda-20cfd4af2831"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":6,"outputs":[{"name":"stdout","text":"double free or corruption (!prev)\nAborted (core dumped)\n","output_type":"stream"}],"id":"2510358b-c716-472d-a0f1-ad7bae1546bb"},{"cell_type":"markdown","source":"What is the reason behind this issue?\n\n- Being good C++ programmers, we have implemented a desctructor for `BigArray()` to deallocate the dynamic array `BigArray::data_` when an object of that class is destroyed.\n- Examining the table above, we see that this implies we get the default versions of the other five special member functions. Should be okay, shouldn't it?\n- Well, no:\n - Examine line 26 closer. There we have an assignment. The right-hand side is the named object `bigArray1`, so it is an l-value.\n - Thus, the copy assignment operator will be used here.\n - Its default version will simply generate a copy of all data members. Hence we get a *flat copy* of `bigArray1::data_` and `bigArray1::data_` and `bigArray2::data_` will point to the same memory address.\n - The destructor, however, assumes *ownership* of `data_`. So when both objects go out-of-scope at the end of the program (in line 28), the d'tor of `bigArray1` and that of `bigArray2` both attempt to free the same memory address.\n- So in the end the reason for the problem is that we neglected the rule of zero/five/six.","metadata":{},"id":"78455c0f-b267-436c-a1da-72951b83651d"},{"cell_type":"markdown","source":"When we implement the special member functions we should, of course, avoid confusion be adhering to the following\n\n[C.22: Make default operations consistent](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-matched)\n\nSo, if we implement the copy assignment for `BigArray` to make it a deep copy, the same should hold for the copy constructor [and naturally the move assignment/constructor] like we did in the part on *move semantics*.","metadata":{},"id":"5d9d26b5-05ef-4932-95fb-d5a14b78b501"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"865bd92a-b7ce-47b9-9058-edcb272db45e"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"719ec3e2-f84b-4109-ab81-ad3b1b8ce71f"}]} \ No newline at end of file +{"metadata":{"kernelspec":{"display_name":"C++17","language":"C++17","name":"xcpp17"},"language_info":{"codemirror_mode":"text/x-c++src","file_extension":".cpp","mimetype":"text/x-c++src","name":"c++","version":"17"}},"nbformat_minor":5,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Move Semantics","metadata":{},"id":"4c244310-2195-4b50-a923-df0b9ba15f7d"},{"cell_type":"markdown","source":"In order to understand what this is about let us take a look at an example from Rainer Grimm's blog [\"Modernes C++ in der Praxis\"](https://www.linux-magazin.de/ausgaben/2012/12/c-11/):","metadata":{},"id":"92b6cf27-b829-4efd-9395-e38f3a330bb4"},{"cell_type":"code","source":"#include <algorithm>\n#include <iostream>\n#include <vector>\n\nclass BigArrayCopy {\n\npublic:\n\n BigArrayCopy( size_t len ) : len_( len ), data_( new int[ len ] ) {}\n\n BigArrayCopy( const BigArrayCopy& other ) : len_( other.len_ ),\n data_( new int[ other.len_ ] ) {\n std::cout << \"copy construction of \" << other.len_ << \" elements\" << std::endl;\n std::copy( other.data_, other.data_ + len_, data_ );\n }\n\n BigArrayCopy& operator=( const BigArrayCopy& other ) {\n\n std::cout << \"copy assignment of \" << other.len_ << \" elements\" << std::endl;\n\n if( this != &other ) {\n\n delete[] data_;\n len_ = other.len_;\n data_ = new int[ len_ ];\n\n std::copy( other.data_, other.data_ + len_, data_ );\n\n }\n\n return *this;\n }\n\n ~BigArrayCopy() {\n if( data_ != nullptr ) {\n delete[] data_;\n }\n }\nprivate:\n\n size_t len_;\n int* data_;\n\n};","metadata":{"trusted":true},"execution_count":2,"outputs":[],"id":"4ffa9b8d-02be-4919-882c-18556a6ded7d"},{"cell_type":"code","source":"int main() {\n \n std::vector< BigArrayCopy > myVec;\n\n BigArrayCopy bArray( 11111111 );\n BigArrayCopy bArray2( bArray );\n\n myVec.push_back( bArray );\n bArray = BigArrayCopy( 22222222 );\n myVec.push_back( BigArrayCopy( 33333333 ) );\n\n}\n\nmain();","metadata":{"trusted":true},"execution_count":3,"outputs":[{"name":"stdout","output_type":"stream","text":"copy construction of 11111111 elements\ncopy construction of 11111111 elements\ncopy assignment of 22222222 elements\ncopy construction of 33333333 elements\ncopy construction of 11111111 elements\n"}],"id":"dd1e7485-7b8e-4728-a217-df899e07d6d8"},{"cell_type":"markdown","source":"Let us analyse the output. Where do the five copies come from?\n<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>\n_____","metadata":{},"id":"333e1d6f-aab5-4c70-859d-db4dafb02981"},{"cell_type":"markdown","source":"We can change that by adding a **move constructor** and a **move assignment operator** to our class:","metadata":{},"id":"6893f7df-99e0-44e9-a79f-8c6e8acba110"},{"cell_type":"code","source":"#include <algorithm>\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::vector;\n\nclass BigArray {\n\npublic:\n\n BigArray( size_t len ) : len_( len ), data_( new int[ len ] ) {}\n\n BigArray( const BigArray& other ) : len_( other.len_ ),\n data_( new int[ other.len_ ] ) {\n cout << \"copy construction of \" << other.len_ << \" elements\" << endl;\n std::copy( other.data_, other.data_ + len_, data_ );\n }\n\n BigArray& operator=( const BigArray& other ) {\n\n cout << \"copy assignment of \" << other.len_ << \" elements\" << endl;\n\n if( this != &other ) {\n\n delete[] data_;\n len_ = other.len_;\n data_ = new int[ len_ ];\n\n std::copy( other.data_, other.data_ + len_, data_ );\n\n }\n\n return *this;\n }\n\n BigArray( BigArray&& other ) : len_( other.len_ ),\n data_( other.data_ ) {\n cout << \"move construction of \" << other.len_ << \" elements\" << endl;\n other.len_ = 0;\n other.data_ = nullptr;\n }\n\n BigArray& operator=( BigArray&& other ) {\n\n cout << \"move assignment of \" << other.len_ << \" elements\" << endl;\n\n if( this != &other ) {\n\n delete[] data_;\n\n len_ = other.len_;\n data_ = other.data_;\n\n other.len_ = 0;\n other.data_ = nullptr;\n }\n\n return *this;\n }\n\n ~BigArray() {\n if( data_ != nullptr ) {\n delete[] data_;\n }\n }\nprivate:\n\n size_t len_;\n int* data_;\n\n};","metadata":{"trusted":true},"execution_count":1,"outputs":[],"id":"e3784252-4e44-4260-a5a1-d2b92f92e264"},{"cell_type":"code","source":"int main() {\n \n std::vector< BigArray > myVec;\n myVec.reserve(2); // get's rid of the final copy operation, when myVec was reallocated\n\n BigArray bArray( 11111111 );\n BigArray bArray2( bArray );\n\n myVec.push_back( bArray );\n bArray = BigArray( 22222222 );\n myVec.push_back( BigArray( 33333333 ) );\n\n}\n\nmain();","metadata":{"trusted":true},"execution_count":4,"outputs":[{"name":"stdout","output_type":"stream","text":"copy construction of 11111111 elements\ncopy construction of 11111111 elements\nmove assignment of 22222222 elements\nmove construction of 33333333 elements\n"}],"id":"ebbe2374-3d00-4340-98a0-9352ff8ca2cb"},{"cell_type":"markdown","source":"As we can see the assignment and copying of the two temporaries in lines # 10 and 11 now uses our **move semantics**.\n\nBut what is does the **\"&&\"** in the interface of the two methods represent? It is an **r-value reference**.\n","metadata":{},"id":"2fead1ad-cee3-4a0d-ba70-b14066a349b9"},{"cell_type":"markdown","source":"### R-Value References\n\nFirst of all, what are \"r-values\"? Complicated question, short (simplified) answer: stuff that can appear on the right-hand side of an assignment, hence the name (originally). Examples of r-values are:\n\n- temporary objects\n- unnamed objects\n- objects whose address is undeterminable\n\nsuch as\n\n```\nint fourtyTwo = 42;\nstd::string a = std::string( \"rhs is an rvalue\");\nstd::string b = std::string( \"r\" ) + std::string( \"-value\" );\nstd::string c = a + b;\nstd::string d = std::move(b);\n```\n\nThe last line is especially interesting. [std::move](https://en.cppreference.com/w/cpp/utility/move) effectively returns an r-value reference for its argument:\n\n\n> std::move is used to indicate that an object t may be \"moved from\", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t. It is exactly equivalent to a static_cast to an rvalue reference type. ","metadata":{},"id":"a2f2682a-1823-46c4-a444-a23fc192ef17"},{"cell_type":"code","source":"int value = 1;\nint& lRef1 = value;\n// int&& rRef1 = value;\nint&& rRef2 = 1;\nconst int& lRef2 = 1;","metadata":{"trusted":true},"execution_count":7,"outputs":[],"id":"7c569151-43ef-44a8-babc-6928ca17b626"},{"cell_type":"markdown","source":"An r-value can bind to an r-value reference, but also to a constant l-value reference. That's why `BigArrayCopy` worked after all. However, binding to an r-value reference, if possible has **higher precedence**. That's what we need in `BigArray` for the move methods.","metadata":{},"id":"1ad6ad50-160f-49d9-b486-2ac84c1cbdbb"},{"cell_type":"markdown","source":"#### STL Containers","metadata":{},"id":"a0fd8007-04cc-43a5-8c29-07de525a132b"},{"cell_type":"markdown","source":"The containers of the STL support move semantics:","metadata":{},"id":"87780808-08d4-4463-8783-5b21114dc9f6"},{"cell_type":"code","source":"%%file std::move.cpp\n\n#include <iostream>\n#include <vector>\n#include <string>\n\nvoid show( const std::vector< std::string >& vector, const std::string& name ) {\n std::cout << \"Length of vector '\" << name << \"': \" << vector.size()\n << \", contents:\" << std::endl;\n for( const auto& str: vector ) {\n std::cout << str << std::endl;\n }\n std::cout << std::endl;\n}\n\nint main() {\n\n std::vector< std::string > src{ \"Move\", \"Semantics\", \"in\", \"the\", \"STL\" };\n show( src, \"src\" );\n\n std::vector< std::string > dst{ std::move(src) };\n show( dst, \"dst\" );\n show( src, \"src\" );\n\n}","metadata":{"trusted":true},"execution_count":20,"outputs":[{"name":"stdout","text":"Overwriting std::move.cpp\n","output_type":"stream"}],"id":"8e2a7e79-bcd5-4207-a36e-72bcae38b187"},{"cell_type":"code","source":"!g++ std::move.cpp","metadata":{"trusted":true},"execution_count":21,"outputs":[],"id":"fb060696-86ce-440e-8193-5cf540ee6a32"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":22,"outputs":[{"name":"stdout","text":"Length of vector 'src': 5, contents:\nMove\nSemantics\nin\nthe\nSTL\n\nLength of vector 'dst': 5, contents:\nMove\nSemantics\nin\nthe\nSTL\n\nLength of vector 'src': 0, contents:\n\n","output_type":"stream"}],"id":"4712b2fd-4f11-4328-889c-bce3ed9c727e"},{"cell_type":"markdown","source":"Additionally the also support **`move iterators`**. See this nice example on [Fluent C++](https://www.fluentcpp.com/2017/04/25/move-iterators).","metadata":{},"id":"b8b8c25b-ac7c-4263-9dcc-5cc1ea0f5408"},{"cell_type":"markdown","source":"# Rule of Six / Five / Zero","metadata":{},"id":"aa620485-bd12-4727-9c7f-d07f66d0e62a"},{"cell_type":"markdown","source":"The advantage of *move semantics* is that it helps to avoid unnecessary copy operations in order to increase performance. However, it also means that a simple class now could implement the following *six different construction/destruction/assigment methods*:","metadata":{},"id":"6549163b-b1f8-4d7b-9126-0674f01d0022"},{"cell_type":"code","source":"class simple {\n \n // (1) Default Constructor\n simple() {};\n \n // (2) Destructor\n ~simple(){};\n \n // (3) Copy Constructor\n simple( const simple& other ) {};\n \n // (4) Copy Assigment\n simple& operator= ( const simple& other ) {\n simple* aux = new( simple );\n // copy stuff from other to aux\n return *aux;\n };\n \n // (5) Move Constructor\n simple( const simple&& other ) {};\n \n // (6) Move Assignment\n simple& operator= ( const simple&& other ) {\n simple* aux = new( simple );\n // move stuff from other to aux\n // and potentially set other to 'zero'\n return *aux;\n };\n}","metadata":{"trusted":true},"execution_count":14,"outputs":[],"id":"381417d5-fe96-4249-a509-29fcfdd5615b"},{"cell_type":"markdown","source":"The question is, what happens, if we implement none or only some of these methods? For example we know that a default constructor will always be generated automatically by the compiler, if we do not interdict this by deleting it.\n\nThe C++ Core Guidelines have something to say on this topic:\n\n[C.20: If you can avoid defining default operations, do](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-zero)\n> In this case the compiler will auto-generate them for you. This is commonly known as **\"the rule of zero\"**.\n\n[C.21: If you define or =delete any copy, move, or destructor function, define or =delete them all](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-five)\n> The semantics of copy, move, and destruction are closely related, so if one needs to be declared, the odds are that others need consideration too. This is commonly known as **\"the rule of five\"**.\n\n**The Rule of Six**<br>\nextends C.21 to include also the default constructor. The latter is special and, thus, not always include.\n\n**But why?**<br>\nBecause the six special member functions are closely related and the C++ standard is a little bit unintuitive w.r.t. auto-generation when you start selectively defining only some of them. The following table is attributed to *Howard Hinnant (2014)*:<br><br>\n<img src=\"https://www.heise.de/imgs/18/3/6/8/1/8/4/5/DefaultDelete-6dc0661da2aa1431.png\">\n<br><br>\nNote that **user-defined** does not only mean that you implement the constructor/assignment operator, but also includes deleting it (`=delete`) and even explicitely requesting the default variant (`=default`). For full details see \n[Programmiersprache C++: Rule of Zero, or Six](https://www.heise.de/blog/Programmiersprache-C-Rule-of-Zero-or-Six-7463520.html), from which we also borrow the following *cautionary* example:","metadata":{},"id":"4ef8a01b-806b-43b5-9401-5b266143b9e8"},{"cell_type":"code","source":"%%file problematic.cpp\n\n#include <cstddef>\n\nclass BigArray {\n\npublic:\n BigArray( std::size_t len ): len_( len ), data_( new int[ len ] ) {}\n\n ~BigArray() {\n delete[] data_;\n }\n\nprivate:\n size_t len_;\n int* data_;\n \n};\n\nint main() {\n \n BigArray bigArray1( 1000 );\n \n BigArray bigArray2( 1000 );\n \n bigArray2 = bigArray1;\n\n}","metadata":{"trusted":true},"execution_count":2,"outputs":[{"name":"stdout","output_type":"stream","text":"Overwriting problematic.cpp\n"}],"id":"ebe84f93-58ad-47c0-8ae6-9817fab5b867"},{"cell_type":"code","source":"!g++ problematic.cpp","metadata":{"trusted":true},"execution_count":4,"outputs":[],"id":"e45f50e1-2fe8-4a3b-ba00-05edcc21a62b"},{"cell_type":"markdown","source":"Okay, compilation worked. However, when we execute the program we get this:","metadata":{},"id":"79d80da7-ecbe-45d9-9fda-20cfd4af2831"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":6,"outputs":[{"name":"stdout","output_type":"stream","text":"double free or corruption (!prev)\nAborted (core dumped)\n"}],"id":"2510358b-c716-472d-a0f1-ad7bae1546bb"},{"cell_type":"markdown","source":"What is the reason behind this issue?\n\n- Being good C++ programmers, we have implemented a desctructor for `BigArray()` to deallocate the dynamic array `BigArray::data_` when an object of that class is destroyed.\n- Examining the table above, we see that this implies we get the default versions of the other five special member functions. Should be okay, shouldn't it?\n- Well, no:\n - Examine line 26 closer. There we have an assignment. The right-hand side is the named object `bigArray1`, so it is an l-value.\n - Thus, the copy assignment operator will be used here.\n - Its default version will simply generate a copy of all data members. Hence we get a *flat copy* of `bigArray1::data_` and `bigArray1::data_` and `bigArray2::data_` will point to the same memory address.\n - The destructor, however, assumes *ownership* of `data_`. So when both objects go out-of-scope at the end of the program (in line 28), the d'tor of `bigArray1` and that of `bigArray2` both attempt to free the same memory address.\n- So in the end the reason for the problem is that we neglected the rule of zero/five/six.","metadata":{},"id":"78455c0f-b267-436c-a1da-72951b83651d"},{"cell_type":"markdown","source":"When we implement the special member functions we should, of course, avoid confusion be adhering to the following\n\n[C.22: Make default operations consistent](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-matched)\n\nSo, if we implement the copy assignment for `BigArray` to make it a deep copy, the same should hold for the copy constructor [and naturally the move assignment/constructor] like we did in the part on *move semantics*.","metadata":{},"id":"5d9d26b5-05ef-4932-95fb-d5a14b78b501"},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"865bd92a-b7ce-47b9-9058-edcb272db45e"},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[],"id":"719ec3e2-f84b-4109-ab81-ad3b1b8ce71f"}]} \ No newline at end of file -- GitLab