1
votes

What does it mean that implicit move constructor does a member-wise move and implicit move assignment operator a member-wise assignment?

From https://en.cppreference.com/w/cpp/language/move_constructor:

For non-union class types (class and struct), the move constructor performs full member-wise move of the object's bases and non-static members, in their initialization order, using direct initialization with an xvalue argument. If this satisfies the requirements of a constexpr constructor, the generated move constructor is constexpr.

From https://en.cppreference.com/w/cpp/language/move_assignment:

For non-union class types (class and struct), the move assignment operator performs full member-wise move assignment of the object's direct bases and immediate non-static members, in their declaration order, using built-in assignment for the scalars, memberwise move-assignment for arrays, and move assignment operator for class types (called non-virtually).

Will the implicit members look like this for the following exemplary class template:

template<class T>
class Holder {
public:
    Holder(int size) : m_size(size) { m_data = new T[m_size]; }

    Holder(Holder && other) :
        m_size(std::move(other.m_size)),
        m_data(std::move(other.m_data))
    {}

    Holder& operator=(Holder && other) {
       if(this == &other) return *this;
       m_data = std::move(other.m_data);
       m_size = std::move(other.m_size);
       return *this;
    }

    ~Holder() { delete [] m_data; }
private:
    T* m_data;
    int m_size;
};

What's more, what will the std::move() in the above example transfer the resources?

1
If you were to implement a move constructor that individually moves each and every base as well as each and every member - and does that in the initialiser list - you would achieve exactly that is meant by a "full member-wise move". Similarly if you implement the move assignment operator to move all bases and all members. - Peter
And moving a raw pointer (like your m_data) will just result in a copy of the pointer which usually breaks the program. m_data(std::exchange(other.m_data, nullptr)) would probably be the correct thing to do in your case. - Ted Lyngmo
Yes, but I mean what the implict destructor and copy operator will do? - michalt38
When a Holder is destroyed, the space that a T* and an int takes up will be freed. Without an explicit destructor, any data pointed at by m_data will be leaked. It will not delete[] it for you. The implicit copy operator will just copy the values. It'll not copy what m_data points at. If you use this, you'll have two objects pointing at the same data and it'll be free'd twice (UB). - Ted Lyngmo

1 Answers

1
votes

If you look further down you linked page, you will see that your classes compiler generated move constructor (and move assignment operator) will actually be Trivial:

Trivial move constructor

The move constructor for class T is trivial if all of the following is true:

  • it is not user-provided (meaning, it is implicitly-defined or defaulted);
  • T has no virtual member functions;
  • T has no virtual base classes
  • the move constructor selected for every direct base of T is trivial;
  • the move constructor selected for every non-static class type (or array of class type) member of T is trivial;

A trivial move constructor is a constructor that performs the same action as the trivial copy constructor, that is, makes a copy of the object representation as if by std::memmove. All data types compatible with the C language (POD types) are trivially movable.

(Emphasis mine)

The two member variables are POD types and therefore are trivially movable. Since your class is not virtual and it holds no non-trivial members it is therefore trivial and all the data members will be copied. As mentioned in the comments, this will lead to double deleting your pointer and UB.

Since this is the case, you need to implement your move semantics properly, by taking ownership of the moved objects pointer and setting it to nullptr. Or better yet, just use std::vector or even std::unique_ptr.