Move Constructor on Derived Object

Move constructor on derived object

rval is not a Rvalue. It is an Lvalue inside the body of the move constructor. That's why we have to explicitly invoke std::move.

Refer this. The important note is

Note above that the argument x is
treated as an lvalue internal to the
move functions, even though it is
declared as an rvalue reference
parameter. That's why it is necessary
to say move(x) instead of just x when
passing down to the base class. This
is a key safety feature of move
semantics designed to prevent
accidently moving twice from some
named variable. All moves occur only
from rvalues, or with an explicit cast
to rvalue such as using std::move. If
you have a name for the variable, it
is an lvalue.

Move constructor for derived class

I would say that your construct is correct except for a little syntax error, you need to qualify base<T> in the initializer list:

derived(derived &&d): base<T>(std::move(d)), t2(std::move(d.t2)){}

First, the order of initialization is independant of the order of the initializer list. Draft n4296 says in 12.6.2 Initializing bases and members [class.base.init] § 13

In a non-delegating constructor, initialization proceeds in the following order:

(13.1) — First, and only for the constructor of the most derived class (1.8), virtual base classes are initialized in
the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes,
where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.

(13.2) — Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list
(regardless of the order of the mem-initializers).

(13.3) — Then, non-static data members are initialized in the order they were declared in the class definition
(again regardless of the order of the mem-initializers).

(13.4) — Finally, the compound-statement of the constructor body is executed.

[ Note: The declaration order is mandated to ensure that base and member subobjects are destroyed in the
reverse order of initialization. —end note ]

We have also §7 or same chapter that says:

The initialization performed by each mem-initializer constitutes a full-expression. Any
expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization.

My understanding is that standard says that in the move ctor for the derived class, things happens in that order:

  • move ctor for base class is called

    • in turn it calls move ctor for T effectively constructing t member of target and eventually zeroing t member of source
  • move ctor for T2 object is called - at that moment, the end of the full expression has not been reached, and only t member of source has eventually been destroyed
  • at the end of the full statement, source object is left in an undetermined state and should no longer be used.

How to implement move-Constructors for derived classes with private members

Derived::Derived(Derived &&rhs)
: Base(std::move(rhs))
, m_dataDerived(std::move(rhs.m_dataDerived))
{}

This code is correct and idiomatic. If you want, you can write it in a way that clarifies how only the Base part is moved:

Derived::Derived(Derived &&rhs)
: Base(std::move(static_cast<Base&&>(rhs)))
, m_dataDerived(std::move(rhs.m_dataDerived))
{}

We only move the Base subobject of the Derived. Indeed, the Base subobject of rhs is in a valid but (by convention) undefined state after calling the Base move constructor with it, so we better not assume anything about it. But we clearly didn't touch m_dataDerived, so moving from it afterwards is fine.

I would advise against writing code like the above (with extra static_cast) though. For starters, the std::move actually becomes pointless (but leaving it out makes the code even less readable). In the context of a move constructor, the intention and effect of move-constructing the base from std::move(rhs) directly should be perfectly clear and idiomatic.

Your first rule ("I've learned, that std::move does nothing, but after std::move(someDObject) the Object someDObject may be canibalized and you should not access someDObject") is also inaccurate:

  1. The object may be cannibalized, but only by operations that can cannibalize. So accessing an object after calling std::move is not necessarily bad (but presumably someone put the std::move there for a reason, and you won't go wrong by assuming cannibalization).

  2. You can access a moved-from object. But you shouldn't do anything with it that makes any assumption about its state (beyond it being valid, which is required for eventual destruction). In standard library terms, you can only use operations on/of the object that have no preconditions. So you can reset a std::unique_ptr that was moved from, you can call size() on a moved-from std::vector and so on.

That's of course not terribly relevant for move construction, but it pays to understand what exactly is happening.

Move constructors in inheritance hierarchy

class Base
{
// data members...
public:
Base(Base&& other) = default;
};

class Derived
{
// data members...
public:
Derived(Derived&& other) : Base(std::move(other)) { ... }
};

The Derived move constructor casts other to an rvalue using std::move, then passes the result to the Base move constructor, which involves an implicit cast from Derived&& to Base&&.

The Base move constructor might steal the guts of the base class members of other, but it can't touch the guts of the derived class members because it only sees a Base&&.

Calling base's move constructor from derived copy constructor

In the copy constructor

Derived(const Derived& other)

std::move(other) will result in an xvalue expression of type const Derived&&.

This is a legal but somewhat weird type: std::move(other) is a temporary object, but you can't move from it, because it is constant. Such references have a limited number of use cases. See the declarations of std::as_const and std::ref for one particular example.

const Derived&& cannot bind to Base&&, that's why during the overload resolution between

Base(const Base&)
Base(Base&&)

the former is chosen by the compiler.

At the risk of getting undefined behaviour, you can cast constness away and write

Derived(const Derived& other) : Base(std::move(const_cast<Derived&>(other))) {}

to call the move constructor of Base. But don't do it in real code.

How does one call a move '&&' constructor in a base class? in C++

In general, marking some variable as rvalue reference with && does not mean that it keeps this status when passing src further. I mean, after all, the variable has a name in the context of the constructor and can therefore obviously be used as lvalue. In fact, the expression src is indeed an lvalue. src is passed to your constructor as rvalue because the caller intended to have it interpreted as such.

Now, in your constructor, the variable is actually named and using this name leads to an expression that needs to be explicitly marked as rvalue reference to be interpreted as such. Otherwise, it just is an lvalue. This is why you have to move it again to be interpreted by the callee (here, MyMovingBase::ctor) as rvalue whose contents are allowed to be moved out of the original object.

Therefore, François' comment is correct. Using

Subclass(Subclass &&src) : MyMovingBase<SubClass>(std::move(src))

does the trick.

Move semantics in derived-to-base class conversions

RVO Optimisation

If no copy elision takes place [...]

Actually, copy elision will not take place (without if).

From C++ standard class.copy.elision#1:

is permitted in the following circumstances [...]:

-- in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object ([...]) with the same type (ignoring cv-qualification) as the function return type [...]

Technically, when you return a derived class and a slicing operation takes place, the RVO cannot be applied.

Technically RVO works constructing the local object on the returning space on the stack frame.

|--------------|
| local vars |
|--------------|
| return addr |
|--------------|
| return obj |
|--------------|

Generally, a derived class can have a different memory layout than its parent (different size, alignments, ...). So there is no guarantee the local object (derived) can be constructed in the place reserved for the returned object (parent).


Implicit move

Now, what about implicit move?

is the buffer object above guaranteed to be moved from???

In short: no. On the contrary, it is guaranteed the object will be copied!

In this particular case implicit move will not be performed because of slicing.

In short, this happens because the overload resolution fails.
It tries to match against the move-constructor (Buffer::Buffer(Buffer&&)) whereas you have a BufferBuild object). So it fallbacks on the copy constructor.

From C++ standard class.copy.elision#3:

[...] if the type of the first parameter of the selected constructor or the return_­value overload is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue.

Therefore, since the first overload resolution fails (as I have said above), the expression will be treated as an lvalue (and not an rvalue), inhibiting the move.

An interesting talk by Arthur O'Dwyer specifically refers to this case. Youtube Video.


Additional Note

On clang, you can pass the flag -Wmove in order to detect this kind of problems.
Indeed for your code:

local variable 'buffer' will be copied despite being returned by name [-Wreturn-std-move]

return buffer;

^~~~~~

<source>:20:11: note: call 'std::move' explicitly to avoid copying

return buffer;

clang directly suggests you to use std::move on the return expression.

Move Constructor calling base-class Move Constructor

actually it should be like this.

Window::Window(Window&& _rhs) : SmartHandle( std::forward<SmartHandle>( _rhs ) )     {     };  // eo mtor 

http://msdn.microsoft.com/en-us/library/ee390914.aspx



Related Topics



Leave a reply



Submit