diff --git a/notebooks/14_MoveSemantics+RuleOfSix.ipynb b/notebooks/14_MoveSemantics+RuleOfSix.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..6edac924c24b8348c89367431af00c30b66a8b89 --- /dev/null +++ b/notebooks/14_MoveSemantics+RuleOfSix.ipynb @@ -0,0 +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\":","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 diff --git a/notebooks/16_MultipleDispatch.ipynb b/notebooks/16_MultipleDispatch.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..a5d0b1edcc0e89f908d6561d4b413b7e63ee154e --- /dev/null +++ b/notebooks/16_MultipleDispatch.ipynb @@ -0,0 +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":"# Multiple Dispatch\n\nIn his [Geocomputing presentation](https://www.geophysik.uni-muenchen.de/en/seminars/seminars/geocomputing-29/t-b-a-6) Roman Freissler mentioned **multiple dispatch** as one of the features of the programming language **Julia**. So what is it?\n\n### Wikipedia:\n> Multiple dispatch or multimethods is a feature of some programming languages in which a function or method can be dynamically dispatched based on the run-time (dynamic) type or, in the more general case, some other attribute of more than one of its arguments.[1] This is a generalization of single-dispatch polymorphism where a function or method call is dynamically dispatched based on the derived type of the object on which the method has been called. Multiple dispatch routes the dynamic dispatch to the implementing function or method using the combined characteristics of one or more arguments.\n\n```\nabstract type SpaceObject end\n\nstruct Asteroid <: SpaceObject\n size::Int \nend\nstruct Spaceship <: SpaceObject\n size::Int \nend \n\ncollide_with(::Asteroid, ::Spaceship) = \"a/s\"\ncollide_with(::Spaceship, ::Asteroid) = \"s/a\"\ncollide_with(::Spaceship, ::Spaceship) = \"s/s\"\ncollide_with(::Asteroid, ::Asteroid) = \"a/a\"\n\ncollide(x::SpaceObject, y::SpaceObject) = (x.size > 100 && y.size > 100) ? \"Big boom!\" : collide_with(x, y)\n\n\njulia> collide(Asteroid(101), Spaceship(300))\n\"Big boom!\"\n\njulia> collide(Asteroid(10), Spaceship(10))\n\"a/s\"\n\njulia> collide(Spaceship(101), Spaceship(10))\n\"s/s\"\n```\n","metadata":{},"id":"559dbab5-4930-41cf-ab02-99ed7e282149"},{"cell_type":"markdown","source":"## C++\nC++ does support *dynamic dispatch*, but only in the form of *single dispatch*. Consider the following example:","metadata":{},"id":"58bd10b2-159d-473b-962d-3b09fd7437cd"},{"cell_type":"code","source":"#include <iostream>\n\nstruct Animal {\n virtual void makeSound() const = 0;\n};\n\nstruct Cat : public Animal {\n void makeSound() const override {\n std::cout << \"Meow\" << std::endl;\n }\n};\n\nstruct Dog : public Animal {\n void makeSound() const override {\n std::cout << \"Wuff\" << std::endl;\n }\n};","metadata":{},"execution_count":3,"outputs":[],"id":"c5d388d8-79d7-49d7-a37c-4805521920a1"},{"cell_type":"code","source":"int main() {\n Cat boss;\n Dog partner;\n Animal* animal1{ &boss };\n Animal* animal2{ &partner };\n animal1->makeSound();\n animal2->makeSound();\n}\n\nmain();","metadata":{},"execution_count":10,"outputs":[{"name":"stdout","text":"Meow\nWuff\n","output_type":"stream"}],"id":"bf13fc00-47e6-449c-af3d-4f45f84559ac"},{"cell_type":"markdown","source":"The decision which member function `makeSound` is to be called is taken at runtime depending on the type of child object of `Animal` the pointer points to. Conceptually this happens by examining the first *hidden argument* of `makeSound( this, ... )`.\n\nWhile C++ does **not support multiple dispatch**, we can emulate it in various ways (examples modified from Wikipedia).\n\n#### Multiple Dispatch via Dynamic Casting","metadata":{},"id":"18e383f7-a368-42f9-b398-c4a8d13fc175"},{"cell_type":"code","source":"%%file md_with_casting.cpp\n\n#include <iostream>\n\nstruct SpaceObject {\n SpaceObject( int in ) : size(in) {};\n virtual void collideWith( SpaceObject& other ) = 0;\n int size;\n};\n\n// Need to split class declaration and method implementation\n// because we can only cast complete types, thus, a forward\n// declaration is not sufficient.\nstruct Asteroid : SpaceObject {\n Asteroid( int in ) : SpaceObject( in ) {};\n void collideWith( SpaceObject& other );\n};\n\nstruct Spaceship : SpaceObject {\n Spaceship( int in ) : SpaceObject( in ) {};\n void collideWith( SpaceObject& other );\n};\n\nvoid Asteroid::collideWith( SpaceObject& other ) {\n\n // dynamic_cast to a pointer type returns NULL if the cast fails\n // (dynamic_cast to a reference type would throw an exception on failure)\n\n if( auto asteroid = dynamic_cast< Asteroid* >( &other ) ) {\n\n // handle Asteroid-Asteroid collision\n std::cout << \"Asteroid-Asteroid collision!\" << std::endl;\n\n } else if( auto spaceship = dynamic_cast< Spaceship* >( &other ) ) {\n\n // handle Asteroid-Spaceship collision\n std::cout << \"Asteroid-Spaceship collision!\" << std::endl;\n\n } else {\n\n // default collision handling here\n std::cout << \"Asteroid-UFO collision!\" << std::endl;\n\n }\n}\n\nvoid Spaceship::collideWith( SpaceObject& other ) {\n\n if( auto asteroid = dynamic_cast< Asteroid* >( &other ) ) {\n\n // handle Spaceship-Asteroid collision\n std::cout << \"Spaceship-Asteroid collision!\" << std::endl;\n\n } else if( auto spaceship = dynamic_cast< Spaceship* >( &other ) ) {\n\n // handle Spaceship-Spaceship collision\n std::cout << \"Spaceship-Spaceship collision!\" << std::endl;\n\n } else {\n\n // default collision handling here\n std::cout << \"Spaceship-UFO collision!\" << std::endl;\n\n }\n}\n\nvoid collide( SpaceObject& x, SpaceObject& y ) {\n if( x.size > 100 && y.size > 100 ) {\n std::cout << \"Big boom!\" << std::endl;\n }\n else {\n x.collideWith( y );\n }\n}\n\nint main() {\n Asteroid asteroid{101};\n Spaceship spaceship{300};\n\n collide( asteroid, spaceship );\n\n asteroid.size = 10;\n spaceship.size = 10;\n collide( asteroid, spaceship );\n collide( asteroid, asteroid );\n collide( spaceship, asteroid );\n collide( spaceship, spaceship );\n}","metadata":{"trusted":true},"execution_count":1,"outputs":[{"name":"stdout","text":"Writing md_with_casting.cpp\n","output_type":"stream"}],"id":"abec11bc-6cc1-4100-a2df-0e308ffc71ed"},{"cell_type":"code","source":"!g++ md_with_casting.cpp","metadata":{"trusted":true},"execution_count":2,"outputs":[],"id":"547cb38a-82e2-4f62-9b6e-23d5462087e6"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":3,"outputs":[{"name":"stdout","text":"Big boom!\nAsteroid-Spaceship collision!\nAsteroid-Asteroid collision!\nSpaceship-Asteroid collision!\nSpaceship-Spaceship collision!\n","output_type":"stream"}],"id":"b60b5e86-e09b-4e48-938d-956626d6b5c9"},{"cell_type":"markdown","source":"#### Multiple Dispatch via Map","metadata":{},"id":"bc7899b8-32f8-4c31-bc5b-7c7f5f43fe49"},{"cell_type":"code","source":"%%file md_with_map.cpp\n#include <cstdint>\n#include <typeinfo>\n#include <unordered_map>\n#include <iostream>\n\nclass Thing {\n\nprotected:\n\n Thing( std::uint32_t cid ) : tid( cid ) {}\n\n const std::uint32_t tid; // type id\n\n using CollisionHandler = void(Thing::*)(Thing& other);\n using CollisionHandlerMap = std::unordered_map< std::uint64_t, CollisionHandler >;\n\n static void addHandler( std::uint32_t id1, std::uint32_t id2, CollisionHandler handler ) {\n collisionCases.insert( CollisionHandlerMap::value_type( key( id1, id2 ), handler ) );\n }\n\n static std::uint64_t key( std::uint32_t id1, std::uint32_t id2 ) {\n return std::uint64_t(id1) << 32 | id2;\n }\n\n static CollisionHandlerMap collisionCases;\n\n public:\n void collideWith( Thing& other ) {\n auto handler = collisionCases.find( key( tid, other.tid ) );\n if ( handler != collisionCases.end() ) {\n ( this->*(handler->second) )( other ); // pointer-to-method call\n // ( this->*handler->second )( other ); // pointer-to-method call\n } else {\n std::cout << \"Collision of unknown type!\" << std::endl;\n }\n }\n};\n\nclass Asteroid: public Thing {\n\n void asteroid_collision( Thing& other ) {\n std::cout << \"Asteroid-Asteroid collision!\" << std::endl;\n }\n\n void spaceship_collision( Thing& other ) {\n std::cout << \"Asteroid-Spaceship collision!\" << std::endl;\n }\n\n public:\n\n Asteroid(): Thing( cid ) {}\n static void initCases();\n static const std::uint32_t cid;\n\n};\n\nclass Spaceship: public Thing {\n\n void asteroid_collision( Thing& other ) {\n std::cout << \"Spaceship-Asteroid collision!\" << std::endl;\n }\n\n void spaceship_collision( Thing& other ) {\n std::cout << \"Spaceship-Spaceship collision!\" << std::endl;\n }\n\n\n public:\n\n Spaceship(): Thing( cid ) {}\n static void initCases();\n static const std::uint32_t cid; // class id\n\n};\n\nThing::CollisionHandlerMap Thing::collisionCases;\nconst std::uint32_t Asteroid::cid = typeid( Asteroid ).hash_code();\nconst std::uint32_t Spaceship::cid = typeid( Spaceship ).hash_code();\n\nvoid Asteroid::initCases() {\n addHandler( cid, cid, CollisionHandler(&Asteroid::asteroid_collision ) );\n addHandler( cid, Spaceship::cid, CollisionHandler(&Asteroid::spaceship_collision ) );\n}\n\nvoid Spaceship::initCases() {\n addHandler( cid, Asteroid::cid, CollisionHandler( &Spaceship::asteroid_collision ) );\n addHandler( cid, cid, CollisionHandler( &Spaceship::spaceship_collision ) );\n}\n\nint main() {\n Asteroid::initCases();\n Spaceship::initCases();\n\n Asteroid a1, a2;\n Spaceship s1, s2;\n\n a1.collideWith( a2 );\n a1.collideWith( s1 );\n\n s1.collideWith( s2 );\n s1.collideWith( a1 );\n}","metadata":{"trusted":true},"execution_count":5,"outputs":[{"name":"stdout","text":"Writing md_with_map.cpp\n","output_type":"stream"}],"id":"00f07cb8-9b9f-49f9-aaa4-251edbedf7a6"},{"cell_type":"code","source":"!g++ md_with_map.cpp","metadata":{"trusted":true},"execution_count":6,"outputs":[],"id":"add9aaee-5fac-4206-8d00-de314f99631e"},{"cell_type":"code","source":"!./a.out","metadata":{"trusted":true},"execution_count":7,"outputs":[{"name":"stdout","text":"Asteroid-Asteroid collision!\nAsteroid-Spaceship collision!\nSpaceship-Spaceship collision!\nSpaceship-Asteroid collision!\n","output_type":"stream"}],"id":"1092b652-e9df-4b12-9295-82fff8923817"},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[],"id":"24efc2a0-8297-489d-abd1-d76abb15711c"}]} \ No newline at end of file