Когда экземпляр производного класса передается как r-значение parent ссылка на ничего не подозревающий метод, последний может юридически изменить содержимое родительского элемента, вызывая несогласованность с любыми дополнительными данными, хранящимися в фактическом объекте.Поэтому класс, предназначенный для расширения, не может полагаться на семантику перемещения по умолчанию.Рассмотрим для тривиального примера:
#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!
}
Чтобы передать передачу ресурса наиболее производному классу, я подумал об использовании виртуальной функции для получения ресурса Move-From в конструкторе Move:
... 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(); /**/
}
Это делает трюк , но имеет две проблемы:
- становится излишне многословным довольно быстро из-за того, что C ++ "забывает", что параметр функции r-значениябыло
&&
(см. необходимость std::move
в строках, помеченных /**/
), - не может быть легко обобщено, когда нужно
yield
'ed.
Есть ли лучшее / каноническое решение?Может быть, я упускаю что-то действительно очевидное.