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,
- gets unnecessarily verbose rather quickly due to C++ "forgetting" that a r-value function parameter was a
&&
(see the need forstd::move
in lines marked/**/
), - can't be generalized easily when more than one object needs to be
yield
'ed.
Is there a better / a canonical solution? Maybe I'm missing something really obvious.