【发布时间】:2019-02-20 16:30:48
【问题描述】:
当派生类实例作为 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构造函数中使用虚函数来获取moved-from资源:
... 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 多个对象时,不容易一概而论。
有更好的/规范的解决方案吗?也许我遗漏了一些非常明显的东西。
【问题讨论】:
-
将基础设为非公开。
-
复制和多态不能混合,这也是真正的移动。如果您重视自己的理智,那么任何具有虚拟内容的类都应该是不可复制和不可移动的。如果您需要制作副本,请使用虚拟克隆成语。
-
@n.m.是这样吗?只要尊重
Bis-anA恰好具有一些进一步的功能,复制(将B的实例作为const A&传递)应该没有问题。它只是忽略除A之外的B提供的任何内容,但不影响对象。还是不行? -
将
B的实例作为const A&传递总是可以的,与复制无关。复制涉及复制构造函数或复制赋值。这些应该是deleted。 -
你的前提是错误的,父类的公共接口应该永远导致派生类中的任何不一致。这样做违反了 is-a 关系或任何你想称之为的关系。
标签: c++ c++14 move object-slicing