Move Semantics
Default Functions
If we don't define member functions such as constructor or the assignment operator in a class then the system defines the functions for us. The functions have "default" behavior and are called default functions. The "default" keyword introduced in C++ 11 states explicitly that the system provide the default implementation for the member function. We are talking about functions such as copy, move constructors, assignment operators. Usually the system will create the default implementation even if we don't say so but in certain cases it won't and we need to explicitly use the "default" keyword . For normal functions there is a bitwise copy of the original object. For the move member functions the system calls the move function on the data member variables and then copies them using the move operators if there are any.File: default1.cpp
#include <iostream> using namespace std ; class A { public: int x1 ; int x2 ; A( ) { cout << "A Empty constructor." << endl ; } }; class B { public: B( ) { cout << "B Empty constructor." << endl ; } B& operator=(B&& other) noexcept { cout << "B Move assignment operator for class B." << endl ; return *this ; } }; class C { public: B BObject1 ; C( ) { cout << "C Empty constructor." << endl ; } C& operator=(C&& obj1) = default ; }; int main() { A AObject1 ; AObject1.x1 = 10 ; AObject1.x2 = 20 ; A AObject2 ; AObject2 = AObject1 ; cout << AObject2.x1 << " " << AObject2.x2 << endl ; C CObject1 ; C CObject2 ; CObject2 = move(CObject1) ; return 0 ; } $ g++ default1.cpp ; ./a.exe A Empty constructor. A Empty constructor. 10 20 B Empty constructor. C Empty constructor. B Empty constructor. C Empty constructor. B Move assignment operator for class B. The statement AObject2 = AObject1 ; uses the default assignment operator which is a bitwise copy and if we print the contents of AObject2 we can see the values "10 20" that were in "AObject1" got copied over to "AObject2" : We then execute the statement: CObject2 = move(CObject1) ; The "move" changes the "CObject1" to a RValue. And in the class we are using the default function that the system generates for a move assignment operator. C& operator=(C&& obj1) = default ; Now the system will take "CObject1" and convert the data members to rvalues and then copy them over to "CObject2" . The class "C" has 1 data member which is "B BObject1 ;" . This is converted to a rvalue and then the class B's mmove assignment gets called.Note that the default move methods may not be created all the time. As an example if a move constructor is already defined for a class then the system expects the move assignment operator to be defined also and will not create it for us by default.
File: default2.cpp
#include <iostream> using namespace std ; class A { public: int x1 ; int x2 ; A( ) { cout << "A Empty constructor." << endl ; } //Move constructor A( A&& obj1 ) { cout << "A Empty constructor." << endl ; } }; int main() { A AObject1 ; AObject1.x1 = 10 ; AObject1.x2 = 20 ; A AObject2 ; AObject2 = move(AObject1) ; cout << AObject2.x1 << " " << AObject2.x2 << endl ; return 0 ; } $ g++ default2.cpp default2.cpp: In function ‘int main()’: default2.cpp:30:29: error: use of deleted function ‘constexpr A& A::operator=(const A&)’ 30 | AObject2 = move(AObject1) ; | ^ default2.cpp:6:7: note: ‘constexpr A& A::operator=(const A&)’ is implicitly declared as deleted because ‘A’ declares a move constructor or move assignment operator 6 | class A | ^ The above code shows that the system does not create a default move assignment operator implementation. If we don't need to use the move assignment operator then the code compiles fine. This can be illustrated by commenting out the code: AObject2 = move(AObject1) ; We can also tell the system to create the method explicitly using the default keyword.
File: default3.cpp
#include <iostream> using namespace std ; class A { public: int x1 ; int x2 ; A( ) { cout << "A Empty constructor." << endl ; } //Move constructor A( A&& obj1 ) { cout << "A Empty constructor." << endl ; } A& operator=(A&& other) = default ; }; int main() { A AObject1 ; AObject1.x1 = 10 ; AObject1.x2 = 20 ; A AObject2 ; AObject2 = move(AObject1) ; cout << AObject2.x1 << " " << AObject2.x2 << endl ; return 0 ; }
Delete keyword
We should use the "default" keyword to make the code readable and to ensure the system is doing what we expect. In addition to the word "default" we also have the word "delete" to indicate that the system should not create the default implementation for that function. This can be useful if we want to prevent certain functionality such as assignments.File: default_del1.cpp
#include <iostream> using namespace std ; class B { public: B( ) { cout << "B constructor." << endl ; } B( const B& obj1 ) { cout << "B copy constructor." << endl ; } B( B&& obj1 ) { cout << "B move copy constructor." << endl ; } B& operator=(B&& other) noexcept { cout << "B Move assignment operator for class B." << endl ; return *this ; } }; class A { public: B objectb1 ; A( ) { cout << "A Empty constructor." << endl ; } A( const A& object1 ) { cout << "A Copy constructor." << endl ; } A& operator =( const A& object1 ) = delete ; ~A() { } }; int main() { A AObject1 ; cout << "---" << endl ; A AObject2 ; AObject2 = AObject1 ; cout << "---" << endl ; return 0 ; } $ g++ default_del1.cpp ; ./a.exe default_del1.cpp: In function ‘int main()’: default_del1.cpp:62:15: error: use of deleted function ‘A& A::operator=(const A&)’ 62 | AObject2 = AObject1 ; | ^~~~~~~~ default_del1.cpp:45:11: note: declared here 45 | A& operator =( const A& object1 ) = delete ; |
Fall Back
If the move copy or the assignment move operators are not defined then the system defaults to the pre C++11 style move and assignment operators.File: default__fall_back1.cpp
#include <iostream> using namespace std ; class A { public: int x1 ; int x2 ; A( ) { cout << "A Empty constructor." << endl ; } A( const A& obj1 ) { cout << "A Copy constructor." << endl ; } //Assignment regular A& operator=( const A& obj1 ) { cout << "Assignment operator." << endl ; x1 = obj1.x1 ; x2 = obj1.x2 ; return *this ; } }; int main() { A AObject1 ; AObject1.x1 = 10 ; AObject1.x2 = 20 ; A AObject2 ; AObject2 = move(AObject1) ; cout << AObject2.x1 << " " << AObject2.x2 << endl ; return 0 ; } $ g++ default_fall_back1.cpp ; ./a.exe A Empty constructor. A Empty constructor. Assignment operator. 10 2
File: default__fall_back2.cpp
#include <iostream> using namespace std ; class A { public: int x1 ; int x2 ; A( ) { cout << "A Empty constructor." << endl ; } }; class B { public: B( ) { cout << "B Empty constructor." << endl ; } /* B& operator=(B& other) noexcept { cout << "B assignment operator for class B." << endl ; return *this ; } */ /* B& operator=(B&& other) noexcept { cout << "B Move assignment operator for class B." << endl ; return *this ; } */ }; class C { public: // B BObject1 ; C& operator=(const C& other) noexcept { cout << "C assignment operator for class C." << endl ; return *this ; } C( const C& other ) { cout << "C Copy constructor." << endl ; } C( ) { cout << "C Empty constructor." << endl ; } // C& operator=(C&& obj1) = default ; }; int main() { C CObject1 ; C CObject2 ; CObject2 = move(CObject1) ; return 0 ; } $ g++ default_fall_back2.cpp ; ./a.exe C Empty constructor. C Empty constructor. C assignment operator for class C. Remove the comments in the above code except the C& operator=(C&& obj1) = default ; and observe the behaviour.
Exercise
1)
File: default_ex1.cpp
#include <iostream> using namespace std ; class B { public: B( ) { cout << "B constructor." << endl ; } B( const B& obj1 ) { cout << "B copy constructor." << endl ; } B( B&& obj1 ) { cout << "B move copy constructor." << endl ; } B& operator=(B&& other) noexcept { cout << "B Move assignment operator for class B." << endl ; return *this ; } }; class A { public: B objectb1 ; A( ) { cout << "A Empty constructor." << endl ; } A( const A& object1 ) { cout << "A Copy constructor." << endl ; } A& operator =( const A& object1 ) { cout << "A Assignment operator." << endl ; return *this ; } ~A() { } //Move constructor // A(A&& other) = default ; }; int main() { A AObject1 ; cout << "---" << endl ; A AObject2 = move(AObject1) ; cout << "---" << endl ; return 0 ; } $ ./a.exe B constructor. A Empty constructor. --- B constructor. A Copy constructor. --- Examine the above code and the output. Did the system create the A's move copy constructor by default ? Remove the comments and observe the behaviour.