C++11/C++14 5B. Move Semantics II - 2020
"If you're an experienced C++ programmer and are anything like me, you initially
approached C++11 thinking, "Yes, yes, I get it. It's C++, only more so." But as you
learned more, you were surprised by the scope of the changes. auto declarations,
range-based for loops, lambda expressions, and rvalue references change the face of
C++, to say nothing of the new concurrency features. And then there are the
idiomatic changes. 0 and typedefs are out, nullptr and alias declarations are in.
Enums should now be scoped. Smart pointers are now preferable to built-in ones.
Moving objects is normally better than copying them.
- Effective Modern C++ by Scott Meyers
// t1.cpp #include <iostream> #include <string> using namespace std; class A { public: // default constructor A() : s("default") {} // copy constructor A(const A& o) : s(o.s) { cout << "move failed!\n";} // move constructor A(A&& o) : s(move(o.s)) {} string printObj() { return s; } private: string s; }; A temp(A a) { return a; } int main() { // move-construct from rvalue temporary A a1 = temp(A()); // move-construct from lvalue cout << "before move() : a1 = " << a1.printObj() << endl; A a2 = move(a1); cout << "after move() : a1 = " << a1.printObj() << endl; cout << "after move() : a2 = " << a2.printObj() << endl; return 0; }
Output:
before move() : a1 = default after move() : a1 = after move() : a2 = default
In the code, we constructed an object of class A (a1) using a returned object (rvalue) from the temp() function. Then, we constructed another A object a2 from a1 using move().
// t2.cpp #include <iostream> #include <string> using namespace std; class A { public: // default constructor A() : s("default") { cout << "default constructor A\n"; } // copy constructor A(const A& o) : s(o.s) { cout << "copy constructor A - move failed!\n";} // move constructor A(A&& o) : s(move(o.s)) { cout << "move constructor A(A&&) called\n"; } string printObj() { return s; } private: string s; }; A temp(A a) { return a; } class B : public A {}; int main() { // calls default constructor B b1; // calls implicit move constructor B b2 = std::move(b1); return 0; }
Since we haven't declared move constructor for B, when we do:
B b2 = std::move(b1);
the implicit move constructor B::(B&&) will be used. It calls A's move constructor A::(A&&). The output looks like this:
$ g++ t2.cpp -o t2 -std=c++11 $ ./t2 default constructor A move constructor A(A&&) called
In this sample code for creating class C object, unlike the previous sample for class B, we defined a destructor.
// t3.cpp #include <iostream> #include <string> using namespace std; class A { public: // default constructor A() : s("default") { cout << "default constructor A\n"; } // copy constructor A(const A& o) : s(o.s) { cout << "copy constructor A - move failed!\n";} // move constructor A(A&& o) : s(move(o.s)) { cout << "move constructor A(A&&) called\n"; } string printObj() { return s; } private: string s; }; A temp(A a) { return a; } class B : public A {}; class C : public B { public: // this destructor prevents implicit move ctor C::(C&&) ~C() {}; };
Since we have our own destructor for C, the move() won't call C::(C&&), instead it uses copy constructor as shown in the output below:
default constructor A copy constructor A - move failed!
If we comment out the destructor for C, we get the following output instead:
default constructor A move constructor A(A&&) called
The following code is almost the same as the code in Sample C. But there is a primary difference. We declared default for the move constructor:
D(D&&) = default;
Here is the code:
// t4.cpp #include <iostream> #include <string> using namespace std; class A { public: // default constructor A() : s("default") { cout << "default constructor A\n"; } // copy constructor A(const A& o) : s(o.s) { cout << "copy constructor A - move failed!\n";} // move constructor A(A&& o) : s(move(o.s)) { cout << "move constructor A(A&&) called\n"; } string printObj() { return s; } private: string s; }; A temp(A a) { return a; } class B : public A {}; class C : public B { public: // this destructor prevents implicit move ctor C::(C&&) //~C() {}; }; class D : public A { public: // default constructor D() {} // destructor would prevent implicit move constructor D::(D&&) ~D() {}; // forced use of move constructor D(D&&) = default; }; int main() { D d1; D d2 = std::move(d1); return 0; }
default constructor A move constructor A(A&&) called
The syntax of forcing use of move constructor is:
class_name ( class_name && ) = default;
As we can see from the output, we can ask the compiler to use move constructor even though we defined destructor.
C++11/C++14 Thread Tutorials
C++11 1. Creating Threads
C++11 2. Debugging with Visual Studio 2013
C++11 3. Threading with Lambda Function
C++11 4. Rvalue and Lvalue
C++11 5. Move semantics and Rvalue Reference
C++11 5B. Move semantics - more samples for move constructor
C++11 6. Thread with Move Semantics and Rvalue Reference
C++11 7. Threads - Sharing Memory and Mutex
C++11 8. Threads - Race Conditions
C++11 9. Threads - Deadlock
C++11 10. Threads - Condition Variables
C++11 11. Threads - unique futures (std::future<>) and shared futures (std::shared_future<>).
C++11 12. Threads - std::promise
C++11/C++14 New Features
initializer_list
Uniform initialization
Type Inference (auto) and Range-based for loop
The nullptr and strongly typed enumerations
Static assertions and Constructor delegation
override and final
default and delete specifier
constexpr and string literals
Lambda functions and expressions
std::array container
Rvalue and Lvalue (from C++11 Thread tutorial)
Move semantics and Rvalue Reference (from C++11 Thread tutorial)
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization