lo tolmencre lo tolmencre - 2 months ago 26
C++ Question

Move semantics with non-pointer data members

Probably this has been asked and answered already, but I don't know what to search for.

Can move semantics be used for non-pointer data members, if the data members have move assignment operators defined?

Suppose I have a class

M
that defines
M::operator=(M&&)
like this:

template <class T>
class M
{
public:
M()
{
mem_M = new T;
}

M& operator=(M&& src)
{
if (this != &src)
{
mem_M = src.mem_M;
src.mem_M = nullptr;
}
return *this;
}

private:
T* mem_M;
};


Now obviously I can have a class
C<T>
like this, with a move constructor that makes no use of
T
's move assignment operator:

template <class T>
class C
{
public:
C ()
{
mem_C = new T;
}
C (C&& rhs)
{
mem_C = rhs.mem_C;
rhs.mem_C = nullptr;
}

private:
T* mem_C;
};


However, what if I wanted
C<T>::mem_C
to not be a pointer but an ordinary member, how would I deal with
C<T>::mem_C
in the move-functions? I can of course invoke the move assignment operator
T::operator=(T&&)
to move the filed
mem_C
from one instance to the other, but how do I properly reset the instance of
C
passed to
C<T>::C(C&&)
?

This at least looks wrong to me:

template <class T>
class C
{
public:
C ()
{
mem_C = T();
}
C (C<T>&& rhs)
{
mem_C = std::move(rhs.mem_C);
rhs.mem_C = T(); // ?? like this?
}

private:
T mem_C;
};


So, what is the standard compliant way to reset non-pointer data members in move functions?

Answer

The move assignment/constructors for the contained types must leave the objects in an "acceptable" state, whatever that means for that type. Nothing outside the type being moved should have any responsibility for maintaining the state of the object.

Also, you want to make sure you're calling move constructors of contained types in your parent move constructor, not the contained type's move assignment as you are in your example:

// move constructor calls move constructor of contained elements
C (C<T>&& rhs) : mem_c(std::move(rhs.mem_c))
{
    // anything in here is using already-constructed data members
}

// move assignment calls move assignment of contained elements
C & operator=(C<T>&& rhs) {
    mem_c = std::move(rhs.mem_c);
}