Reputation: 11550
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.
Upvotes: 5
Views: 514
Reputation: 119847
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.
Upvotes: 4