diff --git a/notebooks/14_MoveSemantics+RuleOfSix.ipynb b/notebooks/14_MoveSemantics+RuleOfSix.ipynb index 67cfdf0a2fb6542e6571e5733e1487542fb6574f..6edac924c24b8348c89367431af00c30b66a8b89 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":"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":4,"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":"# Rule of Six / Five / Zero","metadata":{},"id":"aa620485-bd12-4727-9c7f-d07f66d0e62a"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"a07bc870-4ffc-462a-bce0-ae82a40b625b"}]} \ No newline at end of file +{"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\":","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","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 frome?\n\n\n\n\n\n\n\n \n \n \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","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":{"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":"# 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 // Default Constructor\n simple() {};\n \n // Destructor\n ~simple(){};\n \n // Copy Constructor\n simple( const simple& other ) {};\n \n // 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 // Move Constructor\n simple( const simple&& other ) {};\n \n // 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.","metadata":{},"id":"4ef8a01b-806b-43b5-9401-5b266143b9e8"},{"cell_type":"code","source":"https://www.heise.de/blog/Programmiersprache-C-Rule-of-Zero-or-Six-7463520.html","metadata":{"trusted":true},"execution_count":15,"outputs":[],"id":"ebe84f93-58ad-47c0-8ae6-9817fab5b867"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"e45f50e1-2fe8-4a3b-ba00-05edcc21a62b"}]} \ No newline at end of file