What is the behaviour of compiler generated move constructor?

Frahm picture Frahm · Apr 17, 2013 · Viewed 9.2k times · Source

Does std::is_move_constructible<T>::value == true imply that T has a usable move constructor? If so, what is the default behaviour of it?

Consider the following case:

struct foo {
    int* ptr;
};

int main() {
    {       
        std::cout << std::is_move_constructible<foo>::value << '\n';
        foo f;
        f.ptr = (int*)12;
        foo f2(std::move(f));
        std::cout << f.ptr << ' ' << f2.ptr << '\n';
    }
    return 0;
}

and the output is:

1
0000000C 0000000C

I thought that f.ptr should be nullptr. So in this case,

  1. Is f2 move constructed ?
  2. If so, shouldn't the rvalue be invalidated?
  3. How can I know if instances of a class can be properly move-constructed (invalidate the old one)?

(I'm using VS11.)

Update

The default behaviour of move constructor is same as a copy constructor, is it correct? If it's true,

  1. We always expect a move ctor to steal the resources of the moved-from object, while the default one does not behave as expected, so what's the point of having a default move ctor?
  2. How can I know if a class has a custom move constructor (which can be guaranteed to behave properly)?

It seems that foo f2(std::move(f)); calls the copy ctor when I declared one, see:

struct foo {
    int* ptr;
    foo() {}
    foo(const foo& other) {
        std::cout << "copy constructed\n";
    }
};

int main() {
    {       
        std::cout << std::is_move_constructible<foo>::value << '\n';
        foo f;
        foo f2(std::move(f));
    }
    system("pause");
    return 0;
}

Now the output is:

1
copy constructed

If foo has a move constructor, then wouldn't foo f2(std::move(f)) call it?

So now my questions is: How to know if a class has a move ctor, and if it has one, how can I explicitly call it?

What I'm trying to do is…

template<typename T, bool has_move_ctor>
struct MoveAux;

template<typename T>
struct MoveAux<T, true> {
    static void doMove(T* dest, T* src) {
        new(dest) T(std::move(*src)); //move ctor
    }
};

template<typename T>
struct MoveAux<T, false> {
    static void doMove(T* dest, T* src) {
        new(dest) T(*src); //copy ctor
        src->~T();
    }
};

template<typename T>
inline doMove(T* dest, T* src) {
    MoveAux<T,/*a trait*/>::doMove(dest, src);
}

So I thought std::is_move_constructible<T>::value can be passed to the template, while now I see that this trait only cares if T t(T()) is a valid expression, it may call T::T(const T&). Now assume that T is a custom class, then I want the above templates to behave like:

  1. If I don't declare a move ctor, I want that template method calls the MoveAux<T,false>::doMove.
  2. If I declared one, I need it calls to MoveAux<T,true>::doMove.

Is it possible to make this work?

Answer

Andy Prowl picture Andy Prowl · Apr 17, 2013

does std::is_move_constructible<T>::value == true implies that T has a usable move constructor?

Either a move constructor or a copy constructor. Remember that the operation of copy construction satisfies all the requirements that are placed upon the operation move construction, and some more.

In Standard terms, a MoveConstructible object is one for which the evaluation of the expression:

T u = rv; 

makes u equivalent to the value of rv before the construction; the state of rv after being moved-from is unspecified. But since it is unspecified, this means the state could even be identical to the one rv had before being moved from: In other words, u could be a copy of rv.

In fact, the Standard defines the CopyConstructible concept to be a refinement of the MoveConstructible concept (so everything which is CopyConstructible is also MoveConstructible, but not vice versa).

if so, what is the default behaviour of it?

The behavior of an implicitly generated move constructor is to perform a member-wise move of the data members of the type for which it is generated.

Per Parahgraph 12.8/15 of the C++11 Standard:

The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its bases and members. [ Note: brace-or-equal-initializers of non-static data members are ignored. See also the example in 12.6.2. —end note ]

Moreover:

1 - is f2 move constructed ?

Yes.

2 - if so, shouldn't the rvalue be invalidated?

Moving a pointer is the same as copying it. So no invalidation is going on, neither should it be going on. If you want a move constructor that leaves the moved-from object in a particular state (i.e. sets a pointer data member to nullptr), you have to write your own - or delegate this responsibility to some smart pointer class such as std::unique_ptr.

Notice, that the word "invalidated" is not quite correct here. Move constructors (as well as move assignment operators) are meant to leave the moved-from object in a valid (yet unspecified) state.

In other words, the class invariant needs to be respected - and it should be possible to invoke on a moved-from objects operations that do not have any precondition on its state (usually, destruction and assignment).