当派生的类实例作为对不怀疑方法的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构造函数中获取移出的资源:
... 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(); /**/
}
这可以解决问题,但是有两个问题,
&&
(请参阅标记为std::move
的行中对/**/
的需要,),很快变得不必要地冗长,yield
编辑多个对象时,很难轻易概括。是否有更好的/规范的解决方案?也许我缺少真正明显的东西。
答案 0 :(得分:1)
您几乎永远都不想复制或移动多态对象。它们通常存在于堆中,并且可以通过(智能)指针进行访问。要进行复制,请使用虚拟clone
习惯用法;而且几乎没有理由移动它们。因此,如果您的类具有虚拟析构函数,则应该delete
d分配其他5个成员(如果需要它们来实现虚拟clone
,则将其保护)。
但是在(大多数是假设的)情况下,当您确实需要移动一个多态对象,并且您只有一个基本指针或引用时,您需要意识到,from-from也是对象公共接口的一部分。因此,它需要使整个对象保持一致状态,而不仅仅是基础部分。因此,您需要确保派生的零件知道。尽一切努力。通常,您需要编写一个专用的move-from虚拟函数,并在move构造函数/赋值中调用它:
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
};
现在任何派生工具都可以对从脚下拉出的基础部件做出适当的反应。