When a derived class instance is passed as a r-value parent reference to an unsuspecting method, the latter can legally change the parent's contents, causing incoherence with any extra data stored in the actual object. Therefore a class designed for extension can not rely on default move semantics. Consider for a trivial example:
#include <memory>
#include <utility>
#include <iostream>
struct Resource {
int x;
Resource(int x_) : x(x_*x_) { }
};
struct A {
std::unique_ptr<Resource> ptr;
A(int x) : ptr{std::make_unique<Resource>(x)} { }
A(A&& other) = default; // i.e. : ptr(std::move(other.ptr)) { }
virtual ~A() = default;
// other elements of the rule of 5 left out for brevity
virtual int value() {
return ptr ? ptr->x : 0;
}
};
struct B : A {
int cached;
B(int x) : A(x), cached(A::value()) { }
int value() override {
return cached;
}
int value_parent() {
return A::value();
}
};
int main() {
B b{5};
std::cout << "Before: b.value() = " << b.value()
<< " (parent: " << b.value_parent() << ")\n";
A a = std::move(b);
std::cout << "After: b.value() = " << b.value()
<< " (parent: " << b.value_parent() << ")\n"; // INCONSISTENT!
}
In order to dispatch the resource hand-over to the most derived class, I thought of using a virtual function to get the moved-from resource in the move constructor:
... A {
A(A&& other) : ptr{std::move(other).yield()} { } /**/
virtual std::unique_ptr<Resource>&& yield() && {
return std::move(ptr);
}
... B {
virtual std::unique_ptr<Resource>&& yield() && override {
cached = 0;
return std::move(*this).A::yield(); /**/
}
This does the trick but has two issues,
&&
(see the need for std::move
in lines marked /**/
),yield
'ed.Is there a better / a canonical solution? Maybe I'm missing something really obvious.
You almost never want to copy or move polymorphic objects. They generally live on the heap, and are accessed via (smart) pointers. For copying, use the virtual clone
idiom; and there's almost never a reason to move them. So if your class has a virtual destructor, the other four members of the big 5 should be delete
d (or made protected, if you need them to implement your virtual clone
).
But in a (mostly hypothetical) situation when you do need to move a polymorphic object, and you only have a base pointer or reference, you need to realise that moving-from is also a part of the object's public interface. So it needs to leave the entire object in a consistent state, not just the base part. So you need to make sure the derived parts know. Do whatever it takes. Normally you would want to write a dedicated move-from virtual function, and call it in your move constructor/assignment:
class Base {
virtual void moved_fom() {} // do nothing for base
// some stuff
// members of the big 5
virtual ~Base() = default;
Base (Base&& other) {
// do the move
other->moved_from();
}
// etc
};
Now any derived can properly react to the base part being pulled from under its feet.