Search code examples
c++c++11movemove-constructor

Unmovable struct?


Consider this code with an assumption that the int state marks that a struct has some state and that this state is of any complexity:

struct Object
{
private:
    int state = 0;

public:
    Object( Object&& other ):
      state( std::move( other.state ) )
    {}
};

struct ReferencingObject
{
private:
    Object& object;
    int     state = 0;

public:
    ReferencingObject( Object& object ):
      object( object )
    {}

    ReferencingObject( ReferencingObject&& other ):
      object( other.object ),
      state( std::move( other.state ) )
    {}
};

struct CannotMove
{
    Object              object;
    ReferencingObject   ref;

    CannotMove():
      object(),
      ref( object )
    {}

    CannotMove( CannotMove&& other ):
      object( std::move( other.object ) ),
      ref( std::move( other.ref ) )
    {}
};

1) Focusing on the CannotMove( CannotMove&& ) move constructor, I'd argue that my current implementation is faulty, because moving the ReferencingObject instance keeps reference to a now undefined instance of Object, since it was already moved. However, since ReferencingObject struct contains a state of its own, my conclusion is that there does not exist a correct move constructor for CannotMove. Is my conclusion correct?

2) Let's now assume my conclusion from 1) is correct and that the CannotMove is a fairly complex template class. Complex enough that the only reasonable way of obtaining instances (from the user's point of view) is through the use of auto, such as:

auto cannotMoveInstance = createCannotMove();

The createCannotMove() function could be written in a way that would allow the compiler to perform copy elision and work around the problem from 1) in this particular case entirely. However, to prevent users of the code from accidentally moving an instance of CannotMove, the move constructor should be marked as deleted. This would, however, prevent the compiler from using a copy elision. Am I forced to drop the usage of auto and create my instances directly through

CannotMove  instance;

?

Edit 1: data members of Object and ReferencingObject made private to better reflect typical OOP programming.


Solution

  • Your class invariant appears to be the reference in ref always refers to my own object.

    To maintain this invariant, the proper way to write the move constructor of CannotMove is to move the Object and then construct the ReferencingObject yourself to refer to your own object. First give ReferencingObject a constructor that moves from the state of a ReferencingObject && but sets the reference to a different Object.

    ReferencingObject( ReferencingObject&& other, Object & obj ):
      object( obj ),
      state( std::move( other.state ) )
    {}
    

    then:

    CannotMove( CannotMove&& other ):
      object( std::move( other.object ) ),
      ref( std::move(other.ref), object )
    { }
    

    If you insist on having the code as written, then no, there's no logically correct way to write a constructor of CannotMove because your ReferencingObject exposes no interface to move the state but without copying the reference. There are ways to work around this by only altering CannotMove's definition (e.g., storing a smart pointer to Object instead of an Object directly), but since you don't want to change anything else, then no, it can't be done.

    If you delete the move constructor, then CannotMove can't be returned and must be constructed directly.