Is a `=default` move constructor equivalent to a member-wise move constructor?

C++C++11ConstructorDefaultMove Semantics

C++ Problem Overview


Is this

struct Example { 
    string a, b; 

    Example(Example&& mE) : a{move(mE.a)}, b{move(mE.b)} { }
    Example& operator=(Example&& mE) { a = move(mE.a); b = move(mE.b); return *this; } 
}

equivalent to this

struct Example { 
    string a, b;

    Example(Example&& mE)            = default;
    Example& operator=(Example&& mE) = default;
}

?

C++ Solutions


Solution 1 - C++

Yes both are the same.

But

struct Example { 
    string a, b; 

    Example(Example&& mE)            = default;
    Example& operator=(Example&& mE) = default;
}

This version will permits you to skip the body definition.

However, you have to follow some rules when you declare explicitly-defaulted-functions :

> 8.4.2 Explicitly-defaulted functions [dcl.fct.def.default] > > A function definition of the form: > > attribute-specifier-seqopt decl-specifier-seqopt declarator virt-specifier-seqopt = default ; > > is called an explicitly-defaulted definition. A function that is explicitly defaulted shall > > * be a special member function, > > * have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be “reference to non-const T”, where T is the name of the member function’s class) as if it had been implicitly declared, > > * not have default arguments.

Solution 2 - C++

Yes, a defaulted move constructor will perform a member-wise move of its base and members, so:

Example(Example&& mE) : a{move(mE.a)}, b{move(mE.b)} { }

is equivalent to:

Example(Example&& mE)                 = default;

we can see this by going to the draft C++11 standard section 12.8 Copying and moving class objects paragraph 13 which says (emphasis mine going forward):

> A copy/move constructor that is defaulted and not defined as deleted > is implicitly defined if it is odrused (3.2) or when it is explicitly > defaulted after its first declaration. [ Note: The copy/move > constructor is implicitly defined even if the implementation elided > its odr-use (3.2, 12.2). —end note ][...]

and paragraph 15 which says:

> 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 ] The order of > initialization is the same as the order of initialization of bases and > members in a user-defined constructor (see 12.6.2). Let x be either > the parameter of the constructor or, for the move constructor, an > xvalue referring to the parameter. 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. > > Virtual base class subobjects shall be initialized only once by the > implicitly-defined copy/move constructor (see 12.6.2).

Solution 3 - C++

> Is a =default move constructor equivalent to a member-wise move constructor?

Yes. Update: Well, not always. Look at this example:

#include <iostream>

struct nonmovable
{
    nonmovable() = default;

    nonmovable(const nonmovable  &) = default;
    nonmovable(      nonmovable &&) = delete;
};

struct movable
{
    movable() = default;

    movable(const movable  &) { std::cerr << "copy" << std::endl; }
    movable(      movable &&) { std::cerr << "move" << std::endl; }
};

struct has_nonmovable
{
    movable    a;
    nonmovable b;
    
    has_nonmovable() = default;

    has_nonmovable(const has_nonmovable  &) = default;
    has_nonmovable(      has_nonmovable &&) = default;
};

int main()
{
    has_nonmovable c;
    has_nonmovable d(std::move(c)); // prints copy
}

It prints:

copy

http://coliru.stacked-crooked.com/a/62c0a0aaec15b0eb

You declared defaulted move constructor, but copying happens instead of moving. Why? Because if a class has even a single non-movable member then the explicitly defaulted move constructor is implicitly deleted (such a pun). So when you run has_nonmovable d = std::move(c), the copy constructor is actually called, because the move constructor of has_nonmovable is deleted (implicitly), it just doesn't exists (even though you explicitly declared the move constructor by expression has_nonmovable(has_nonmovable &&) = default).

But if the move constructor of non_movable was not declared at all, the move constructor would be used for movable (and for every member that has the move constructor) and the copy constructor would be used for nonmovable (and for every member that does not define the move constructor). See the example:

#include <iostream>

struct nonmovable
{
    nonmovable() = default;

    nonmovable(const nonmovable  &) { std::cerr << "nonmovable::copy" << std::endl; }
    //nonmovable(      nonmovable &&) = delete;
};

struct movable
{
    movable() = default;

    movable(const movable  &) { std::cerr << "movable::copy" << std::endl; }
    movable(      movable &&) { std::cerr << "movable::move" << std::endl; }
};

struct has_nonmovable
{
    movable    a;
    nonmovable b;
    
    has_nonmovable() = default;

    has_nonmovable(const has_nonmovable  &) = default;
    has_nonmovable(      has_nonmovable &&) = default;
};

int main()
{
    has_nonmovable c;
    has_nonmovable d(std::move(c));
}

It prints:

movable::move
nonmovable::copy

http://coliru.stacked-crooked.com/a/420cc6c80ddac407

Update: But if you comment out the line has_nonmovable(has_nonmovable &&) = default;, then copy will be used for both members: http://coliru.stacked-crooked.com/a/171fd0ce335327cd - prints:

movable::copy
nonmovable::copy

So probably putting =default everywhere still makes sense. It doesn't mean that your move expressions will always move, but it makes chances of this higher.

One more update: But if comment out the line has_nonmovable(const has_nonmovable &) = default; either, then the result will be:

movable::move
nonmovable::copy

So if you want to know what happens in your program, just do everything by yourself :sigh:

Solution 4 - C++

apart very pathological cases ... YES.

To be more precise, you have also to considered eventual bases Example may have, with exact same rules. First the bases -in declaration order- then the members, always in declaration order.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVittorio RomeoView Question on Stackoverflow
Solution 1 - C++Pierre FourgeaudView Answer on Stackoverflow
Solution 2 - C++Shafik YaghmourView Answer on Stackoverflow
Solution 3 - C++anton_rhView Answer on Stackoverflow
Solution 4 - C++Emilio GaravagliaView Answer on Stackoverflow