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:
#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:
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:
#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
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
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.