Frahm
Frahm

Reputation: 815

What is the behaviour of compiler generated move constructor?

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?

Upvotes: 39

Views: 12207

Answers (6)

balki
balki

Reputation: 27664

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

No. It's wrong. It's true only for primitives. It's similar to that of copy constructor.

The default generated copy constructor calls the copy constructor of all its members in the declared order

But The default generated move constructor calls the move constructor of all its members in the declared order

Now the next question is, what is the copy/move constructor of the primitives ints floats pointers do?

Answer: They just copy the values (both copy and move constructor)

Upvotes: 7

qqqqq
qqqqq

Reputation: 923

if foo has a move constructor, then wouldn't foo f2(std::move(f)) calls it? You do not get the default move constructor when you supply your copy constructor. Add following line to get it ( and notice the change ). foo(foo&& ifm)=default;

Upvotes: 0

Andy Prowl
Andy Prowl

Reputation: 126432

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

Upvotes: 28

Mr.C64
Mr.C64

Reputation: 42944

Note that Visual Studio 2012 / VC++11 does not support compiler generated move constructors; in fact, consider this quote from "C++11 Features in Visual C++ 11" blog post (emphasis mine):

Rvalue references v3.0 adds new rules to automatically generate move constructors and move assignment operators under certain conditions. This will not be implemented in VC11, which will continue to follow VC10's behavior of never automatically generating move constructors/move assignment operators.

With raw pointers, you have to define move constructors by yourself, manually clearing the old "moved-from" pointer:

class Foo 
{
public:

    // Move constructor
    Foo(Foo&& other)
        : m_ptr(other.m_ptr) // copy pointer value
    {
        // Clear out old "moved-from" pointer, to avoid dangling references
        other.m_ptr = nullptr;
    }

private:
    int* m_ptr;
};

Instead, if you use a smart pointer like std::unique_ptr, move constructor is properly defined, and you can just call std::move:

class Foo 
{
public:

    // Move constructor
    Foo(Foo&& other)
        : m_ptr(std::move(other.m_ptr)) // move from other, 
                                        // old pointer automatically cleared
    {
    }

private:
    std::unique_ptr<int> m_ptr;
};

With automatically generated move constructors, you don't have to define a custom move constructor explicitly, if member-wise move is OK for you.

Upvotes: 2

ForEveR
ForEveR

Reputation: 55887

n3376 12.8/15

The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its bases and members.

Each base or non-static data member is copied/moved in the manner appropriate to its type:

— if the member is an array, each element is direct-initialized with the corresponding subobject of x;

— if a member m has rvalue reference type T&&, it is direct-initialized with static_cast(x.m);

— otherwise, the base or member is direct-initialized with the corresponding base or member of x.

Upvotes: 0

Nicol Bolas
Nicol Bolas

Reputation: 473447

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

No. It states that you can take an rvalue expression of the object type and construct an object from it. Whether this uses the move constructor or the copy constructor is not relevant to this trait.

is f2 move constructed ?

Yes.

if so, shouldn't the rvalue be invalidated?

No. That's not how movement works.

how can I know if instances of a class can be properly move-constructed(invalidate the old one)?

That is not any definition of "properly move-constructed" that exists. If you want to "invalidate the old one", then you will have to do that yourself.

Move construction generally guarantees nothing about the state of the old object. It will be in a valid but undefined state. Such state very much can be "the same as it was before". Move construction for a pointer is the same as copying the pointer.

If you want to "invalidate" after a move, then you need to write your own move constructor that explicitly does that.

(I'm using VS11)

Then you have no compiler-generated move constructors at all. Not that it would matter, since the move and copy constructors for pointers both do the same thing.

Upvotes: 5

Related Questions