C++: Implementation of Move Construction and Move Assignment

Thomas Becker has a very good
article
on rvalue references and move semantics.
Unfortunately, his article lacks a comprehensive discussion how to
implement the move operations. This post tries to fill the gap.

Example

Assume a resource controlling wrapper class around a connection construct
provided by a C library.
We're interested here only in creating and closing such a
connection, and here's the relevant part of the C header:

The constructor creates the connection, stores it and cares for errors.
The destructor cleans up, and we provide an accessor function.
In our case it isn't useful to copy such connections, so we forbid it.

Move

While copying is not very useful for Connection,
moving it makes sense.
So we provide move construction and move assignment.
One thing to remember about moving is that it typically changes both
objects, the moved-from and the moved-to object.

If you think about real things, this becomes obvious:
if you move real matter from one table to the other,
the original space is now empty (actually probably filled by air now).
For C++, if you move from oldObj to newObj,
oldObj still stays around and is eventually destructed.
You need to make sure that the destruction of oldObj
doesn't cause any problems, e.g. by marking it invalid or empty.

In our example, if we move from one Connection con1 to
con2,
we must make sure that we don't call close_connection
on the destruction of con1 (given that we didn't assign
another value to it).
For this, we use the special value BadConnection and
check ib the destructor for it. To be able to re-use this code later,
we also factor it out into a separate function:

Move Constructor

One difference to the copy constructor is that we don't take the
other object by const reference:

Connection(Connection &&other)

As stated above, we need to modify the other object when moving.

In the member initializer list we take over the data from the
other object.
A more general version would be:

: con(std::move(other.con))

This version also makes it more explicit that we want to move the data.
However, as connection_id is a C type, there's no real
difference between moving and copying.

And finally, in the body, we set the other object to an invalid state.

Move Assignment

Assignment is logically the combination of the destruction of the old
value and construction of the new one. For move assignment, the construction
is done by move construction.
So here's the code that combines the actions of the destructor and the move
constructor:

Again, we don't take other by const reference because we need
to modify rhs.
Then we check for self assignment (more on this later).
After that, we do the actions of the destructor (closeCon(con))
and the move constructor
(again, con = std::move(other.con) would be more general,
but doesn't make a difference here) and finally return our own object.

This is now a perfectly valid implementation and probably as efficient
as it gets.
Maybe you heard at some time that this form of check against self assigment
is not the preferred way to do it, but here (and for now) it's ok.
I'll cover more of move assignment implementation options
(including self-assignment, whether you should mark move operations as
noexcept (yes, you should), and exceptions on move in general)
in another blog post.