This is the move constructor of class X
:
X::X(X&& rhs)
: base1(std::move(rhs))
, base2(std::move(rhs))
, mbr1(std::move(rhs.mbr1))
, mbr2(std::move(rhs.mbr2))
{ }
These are the things I'm wary about:
- We're moving from
rhs
twice andrhs
isn't guaranteed to be in a valid state. Isn't that undefined behavior for the initialization ofbase2
? - We're moving the corresponding members of
rhs
tombr1
andmbr2
but sincerhs
was already moved from (and again, it's not guaranteed to be in a valid state) why should this work?
This isn't my code. I found it on a site. Is this move constructor safe? And if so, how?
No, that would be an example of possible undefined behavior. It might work a lot of the time, but it's not guaranteed to. C++ allows it, but you are the one who has to make sure you don't try to use
rhs
again in that way. You are trying to userhs
three times afterits guts had been ripped outitsrvalue
reference was passed to a function that might potentially move from it (leaving it in an undefined state).This is approximately how an implicit move constructor typically works: each base and member subobject is move-constructed from the corresponding subobject of
rhs
.Assuming that
base1
andbase2
are bases ofX
that do not have constructors takingX
/X&
/X&&
/const X&
, it's safe as written.std::move(rhs)
will implicitly convert tobase1&&
(respectivelybase2&&
) when passed to the base class initializers.EDIT: The assumption has actually bitten me a couple of times when I had a template constructor in a base class that exactly matched
X&&
. It would be safer (albeit incredibly pedantic) to perform the conversions explicitly:or even just:
which I believe should exactly replicate what the compiler would generate implicitly for
X(X&&) = default;
if there are no other base classes or members thanbase1
/base2
/mbr1
/mbr2
.EDIT AGAIN: C++11 §12.8/15 describes the exact structure of the implicit member-wise copy/move constructors.
It depends on the inheritance hierarchy. But chances are very good that this code is fine. Here is a complete demo showing that it is safe (for this specific demo):
which should output:
Here you see that each base and each member is moved exactly once.
Remember:
std::move
doesn't really move. It is just a cast to rvalue, nothing more.So the code casts to rvalue
X
and then passes that down to the base classes. Assuming the base classes look like I have outlined above, then there is an implicit cast to rvaluebase1
andbase2
, which will move construct those two separate bases.Also,
Remember: A moved-from object is in a valid but unspecified state.
As long as the move constructor of
base1
andbase2
don't reach up into the derived class and altermbr1
ormbr2
, then those members are still in a known state and ready to be moved from. No problems.Now I did mention that problems could occur. This is how:
In this example,
base1
has a templated constructor that takes an rvalue-something. If this constructor can bind to an rvalueX
, and if this constructor will move from the rvalueX
, then you have problems:The way to fix this problem (which is relatively rare, but not vanishingly rare), is to
forward<base1>(rhs)
instead ofmove(rhs)
:Now
base1
sees an rvaluebase1
instead of an rvalueX
, and that will bind to thebase1
move constructor (assuming it exists), and so you again get:And all again is good with the world.