Implicitly-declared move assignment operator
If no user-defined move assignment operators are provided for a class type, and all of the following is true:
- there are no user-declared copy constructors;
- there are no user-declared move constructors;
- there are no user-declared copy assignment operators;
- there is no user-declared destructor,
See the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | #include <iostream> #include <string> #include <utility> struct A { std::string s; A() : s( "test" ) {} A( const A& o) : s(o.s) { std::cout << "move failed!\n" ; } A(A&& o) : s(std::move(o.s)) {} A& operator=( const A& other) { s = other.s; std::cout << "copy assigned\n" ; return * this ; } A& operator=(A&& other) { s = std::move(other.s); std::cout << "move assigned\n" ; return * this ; } }; A f(A a) { return a; } struct B : A { std::string s2; int n; // implicit move assignment operator B& B::operator=(B&&) // calls A's move assignment operator // calls s2's move assignment operator // and makes a bitwise copy of n }; struct C : B { ~C() {} // destructor prevents implicit move assignment }; struct D : B { D() {} ~D() {} // destructor would prevent implicit move assignment D& operator=(D&&) = default ; // force a move assignment anyway }; int main() { A a1, a2; std::cout << "Trying to move-assign A from rvalue temporary\n" ; a1 = f(A()); // move-assignment from rvalue temporary std::cout << "Trying to move-assign A from xvalue\n" ; a2 = std::move(a1); // move-assignment from xvalue std::cout << "\nTrying to move-assign B\n" ; B b1, b2; std::cout << "Before move, b1.s = \"" << b1.s << "\"\n" ; b2 = std::move(b1); // calls implicit move assignment std::cout << "After move, b1.s = \"" << b1.s << "\"\n" ; std::cout << "\nTrying to move-assign C\n" ; C c1, c2; c2 = std::move(c1); // calls the copy assignment operator std::cout << "\nTrying to move-assign D\n" ; D d1, d2; d2 = std::move(d1); } |
g++ -std=c++20 -pthread a.cpp -o a . /a |
The outupt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Trying to move-assign A from rvalue temporary move assigned Trying to move-assign A from xvalue move assigned Trying to move-assign B Before move, b1.s = "test" move assigned After move, b1.s = "" Trying to move-assign C copy assigned Trying to move-assign D move assigned |
Virtual Inheritance
As with copy assignment, it is unspecified whether virtual base class subobjects that are accessible through more than one path in the inheritance lattice, are assigned more than once by the implicitly-defined move assignment operator. In the code below it is called twice:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <iostream> struct V { V& operator=(V&& other) { // this may be called once or twice // if called twice, 'other' is the just-moved-from V subobject std::cout << "move assigned\n" ; return * this ; } }; struct A : virtual V {}; // operator= calls V::operator= struct B : virtual V {}; // operator= calls V::operator= struct C : B, A {}; // operator= calls B::operator=, then A::operator= // but they may only call V::operator= once int main() { C c1, c2; c2 = std::move(c1); } |
g++ -std=c++20 -pthread b.cpp -o b |
1 2 3 4 5 6 7 | b.cpp: In function ‘int main()’: b.cpp:17:8: warning: defaulted move assignment for ‘B’ calls a non-trivial move assignment operator for virtual base ‘V’ [-Wvirtual-move-assign] 17 | struct B : virtual V {}; // operator= calls V::operator= | ^ b.cpp:16:8: warning: defaulted move assignment for ‘A’ calls a non-trivial move assignment operator for virtual base ‘V’ [-Wvirtual-move-assign] 16 | struct A : virtual V {}; // operator= calls V::operator= | ^ |
./b |
1 2 | move assigned move assigned |
Adding virtual
keyword to operator=
does not help.
MSCV does the same, but does not show warnings:
cl /std:c++20 /EHsc b.cpp |
Deleted move assignment operator
The implicitly-declared or defaulted move assignment operator for class T
is defined as deleted if any of the following conditions is satisfied:
T
has a non-static data member of a const-qualified non-class type (or possibly multi-dimensional array thereof).T
has a non-static data member of a reference type.- etc..
Copy elision
https://en.cppreference.com/w/cpp/language/copy_elision
T x = T(T(f())); // x is initialized by the result of f() directly; no move
Value categories
https://en.cppreference.com/w/cpp/language/value_category
Each C++ expression (an operator with its operands, a literal, a variable name, etc.) is characterized by two independent properties: a type and a value category.