Move assignment operator in C++

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:

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

1 Response to Move assignment operator in C++

  1. dmitriano says:

    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.

Leave a Reply

Your email address will not be published. Required fields are marked *