How to Define a Move Constructor

How to define a move constructor?

  1. MSVC++ implemented move constructors before the final version of the standard was out. In the version of the standard MSVC++'s implementation was based on, the rules for generating a default move constructor were ridiculously more strict than they are in the final version of the standard. See here: Why is this code trying to call the copy constructor? (specifically this answer and the comments on it) for more info on that. This has not been and will not be fixed in Visual Studio 11, for some unknown stupid reason because they had other priorities.

  2. No, you need to call std::move on the members of rcOther, and you initialise members with the corresponding members from the dying object (you misnamed miSize):

    MyClass( MyClass&& rcOther )
    : mpiSize( std::move(rcOther.mpiSize) )
    , miSize2( std::move(rcOther.miSize2) )
    {
    rcOther.mpiSize = 0;
    }

    It doesn't make a difference for built in types like int and int*, but it definitely makes a difference for user-defined types.

    • The reason for this is that std::move just returns the argument casted into a T&&, an rvalue-reference, so that the correct constructor (the move constructor, T(T&&)) is called for each of the sub-objects. If you don't use std::move on the members of the dying object, they will be treated like T&, and the copy constructor of your subobjects (T(T&)) will be called instead of the move constructor. That is very bad and thwarts almost the entire purpose of you having written a move constructor.



3. You are doing some unnecessary things, like setting the integer to 0. You only need to set the pointer to 0 so that deleteing it won't delete the resource of the new object you created.

Also, if this is not a didactic exercise, you may want to consider using std::unique_ptr instead of managing the lifetime of your own object. This way you don't even have to write a destructor for your class. Note that if you do that, using std::move to initialise the member from the dying member in the move constructor would be mandatory.



  • Konrad Rudolph in his answer has caught the fact that your class manages a non-automatic resource but does not follow the Rule of Five Three, Four, or Five. See his answer for more details on this.

How to properly define a move constructor?

The proper generic way is to move-construct each member, but that's what the defauted version does anyway:

T(T && rhs)
: a(std::move(rhs.a))
, b(std::move(rhs.b))
{ }

As a rough rule, you should use the default definition if this is all you need, and you should write an ex­pli­cit move constructor if you're doing something that ex­pli­citly implements move semantics, such as a unique-ownership resource manager:

URM(URM && rhs)
: resource(rhs.resource)
{
rhs.resource = nullptr;
}

The indicator for whether this is appropriate is probably whether your class has a user-defined de­struc­tor. In the example, the destructor would release the managed resource, and this must happen only once, so the moved-from object must be modified.


This is unrelated, but since you are mentioning the assignment operator, here's the popular swap-and-assign/swap idiom:

void swap(URM & rhs) noexcept      // assume members are noexcept-swappable!
{
using std::swap;
swap(resource, rhs.resource);
// ...
}

URM & operator=(URM rhs) noexcept // pass by value
{
rhs.swap(*this);
return *this;
}

The beauty of this approach is that you only need one single version of the assignment operator that works for temporaries and non-temporaries alike, using move construction when appropriate, and as long as all your members are well-designed, you also only need one single swap function. On top of that, if the swap function doesn't throw (which a well-designed class should allow), then your assignment operator doesn't throw, since all the possible exceptions would already occur at the call site.

Best C++ move constructor implementation practice

One way is to implement the default constructor, copy constructor and swap function.

And then implement the move constructor, copy and move assignment operators using the first three.

E.g.:

struct X
{
X();
X(X const&);
void swap(X&) noexcept;

X(X&& b)
: X() // delegate to the default constructor
{
b.swap(*this);
}

// Note that this operator implements both copy and move assignments.
// It accepts its argument by value, which invokes the appropriate (copy or move) constructor.
X& operator=(X b) {
b.swap(*this);
return *this;
}
};

If you have been using this idiom in C++98, then once you add the move constructor you get the move assignment without writing a single line of code.

In some cases this idiom may be not the most efficient. Because the copy operator always first constructs a temporary and then swaps with it. By hand coding the assignment operators it may be possible to get better performance. When in doubt, check optimized assembly output and use a profiler.

Which copy/move constructor/operator to define for simple structures?

Q1: Can you rely on the compiler to auto-generate those?

Yes (in your example). See the C++11 Standard (clause 12), or the article Implicit Move Won’t Go! (nice diagram near the end). To sum up (and simplify), all the following special member functions will be auto-generated (implicitly declared and defined as defaulted):

  • Destructor – because you didn't declare it.
  • Copy Constructor – because you didn't declare it nor any of MC and MAO.
  • Copy Assignment Operator – because you didn't declare it nor any of MC and MAO.
  • Move Constructor – because you didn't declare it nor any of D, CC, CAO and MAO.
  • Move Assignment Operator – because you didn't declare it nor any of D, CC, CAO and MC.

(I used ugly initials only to keep the list items one line each.) In addition to the "because"s above, for all but the Destructor there's the additional constraint that the generated defaults must make sense, i.e. all the data members must be copyable (for the CC and CAO) or movable (for the MC and MAO). (Actually the precise rules are a bit more complicated, but I don't want to rephrase the Standard here.)

Q2: Are the auto-generated functions correct?

Yes (in your example). All your data members (here plain ints) have correct copy/move semantics (their copy/move constructors and assignment operators do the right thing, and those auto-generated for Rect will call them).

Q3: Anyway, should you define them manually?

I see no advantage to it (in your example), and potential issues (as in your example, see comments).

Implicitly generate move constructor

This doesn't compile, because the implicitly declared copy constructor cannot copy p_. (12.8/7)

There is no need for a copy constructor. This does not compile because your compiler does not seem to generate a move constructor automatically, which it should.

There is no way around that other than implementing it yourself or updating the compiler.

Do move constructors and move assignment operators make sense in classes that don't hold pointers as member variables and don't manage resources?

An explicit move constructor or a move operator can be specified for any class, whether it has pointer members or not. It is true that move constructors and operators are usually used with classes that have pointers of some kind. But that's only because in these cases there's a way to effect a move that avoids the more expensive overhead of copying.

A default move operation for a trivial type is equivalent to a copy operation.

A default move operation for a class is equivalent to a move operation for each member of the class.

Therefore, in your example, the move operation is equivalent to a copy operation, and has no observable differences.

However it is certainly possible to need a move operator for a class that has no pointer members, for whatever reason. A classical example is std::thread, which has a move constructor and an assignment operator but not the equivalent copy constructor and copy assignment operator. The contents of std::thread are implementation defined, but typically consist of an opaque handle for an operating system-specific thread identifier. This handle cannot be "copied" in any meaningful way, but its ownership can be meaningful transferred to another std::thread, and that's why you have move operators there.

Your own classes may have similar semantical requirements, too, even if they don't have any pointers of any kind.

How does the move constructor look like if I have a vector (or anything like it) member variable?

Whatever a std::vector needs to do in order to move correctly, will be handled by its own move constructor.

So, assuming you want to move the member, just use that directly:

Shoplist(Shoplist&& other)
: slist(std::move(other.slist))
{}

and

Shoplist& Shoplist::operator=(Shoplist&& other)
{
slist = std::move(other.slist);
return *this;
}

In this case, you could as AndyG points out, just use = default to have the compiler generate exactly the same move ctor and move assignment operator for you.

Note that explicitly destroying the original as you did is definitely absolutely wrong. The other member will be destroyed again when other goes out of scope.


Edit: I did say assuming you want to move the member, because in some cases you might not.

Generally you want to move data members like this if they're logically part of the class, and much cheaper to move than copy. While std::vector is definitely cheaper to move than to copy, if it holds some transient cache or temporary value that isn't logically part of the object's identity or value, you might reasonably choose to discard it.

Understanding the reasoning between copy/move constructors and operators

Congratulations, you found a core issue of C++!

There are still a lot of discussions around the behavior you see with your example code.

There are suggestions like:

A&& helper_alt(A a) {
std::cout << ".." << std::endl;
return std::move(a);
}

This will do what you want, simply use the move assignment but emits a warning from g++ "warning: reference to local variable 'a' returned", even if the variable goes immediately out of scope.

Already other people found that problem and this is already made a c++ standard language core issue

Interestingly the issue was already found in 2010 but not solved until now...

To give you an answer to your question "In the last case, why is the move constructor being called and then the move assignment operator, instead of just the move assignment operator?" is, that also C++ committee does not have an answer until now. To be precise, there is a proposed solution and this one is accepted but until now not part of the language.

From: Comment Status

Amend paragraph 34 to explicitly exclude function parameters from copy elision. Amend paragraph 35 to include function parameters as eligible for move-construction.

Move Constructor & Move Assignment

Since this question was answered in the comments I thought I'd follow the advice from the meta: Question with no answers, but issue solved in the comments (or extended in chat) and write a short Community Wiki to close and answer the question.

I will also add useful additional info and tips from other users who joined the discussion in the comments.

Bo Presson answering and providing additional info on template placement:

The move assignment seems reasonable, except that putting templates in
a cpp file makes them usable in that cpp file only. See
Why can templates only be implemented in the header file?

Rakete1111 clarifying a misconception I had regarding move semantics:

std::move != move semantics. You have move semantics, where rvalues
can be moved (using the move constructor) instead of copied. std::move
is just a facility to enable move semantics (like using the move
constructor) for types that are not rvalues.

kim366 bringing up return optimization question with Jive Dadson and I answering:

... Also, is there really no return-value-optimization, if you don't
have overloaded move ctors/assignments ? -kim366

It seems so, in the example (see function below) he says that z = x +
y + z
will copy the return result twice "If a Vector is large, say,
10,000 doubles, that could be embarrassing." But "Given that
definition, the compiler will choose the move constructor to implement
the transfer of the return value..." He invented c++ so ill just take
his word for it :). Vector operator+(const Vector& a, const Vector&
b) { if (a.size()!=b.size()) throw Vector_size_mismatch{}; Vector
res(a.size()); for (int i=0; i!=a.size(); ++i) res[i]=a[i]+b[i];
return res; }
- hammeramr

(Example was from the book: "The C++ programing language 4th edition"
by Bjarne Stroustrup
)

See also
What is the copy-and-swap idiom?
-Jive Dadson

Hope people find this useful and thanks for those who participated.

What does explicitly-defaulted move constructor do?

What does explicitly-defaulted move constructor do?

It will initialize its member variables with the corresponding member variables in the moved from object after turning them into xvalues. This is the same as if you use std::move in your own implementation:

Base(Base&& other) noexcept : id(std::move(other.id)) {}

Note though that for fundamental types, like int, this is no different from copying the value. If you want the moved from object to have its id set to 0, use std::exchange:

Base(Base&& other) noexcept : id(std::exchange(other.id, 0)) {}

Base& operator=(Base&& other) noexcept {
if(this != &other) id = std::exchange(other.id, 0);
return *this;
}

With that, the Foo move constructor and move assignment operator can be defaulted and "do the right thing".


Suggestion: I think Base would benefit from a constructor that can initialize id directly. Example:

Base(int Id) : id(Id) {
std::cout << "Base() called " << id << std::endl;
}

Base() : Base(0) {} // delegate to the above

Now, the Foo constructor taking an id as an argument could look like this:

Foo(int Id) : Base(Id) {
std::cout << "Foo() called " << id << std::endl;
}


Related Topics



Leave a reply



Submit