【问题标题】:How to prevent move slicing?如何防止移动切片?
【发布时间】: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 值函数参数是 &amp;&amp;(请参阅标记为 /**/ 的行中对 std::move 的需要),因此会很快变得不必要地冗长,
  • 当需要yield'ed 多个对象时,不容易一概而论。

有更好的/规范的解决方案吗?也许我遗漏了一些非常明显的东西。

【问题讨论】:

  • 将基础设为非公开。
  • 复制和多态不能混合,这也是真正的移动。如果您重视自己的理智,那么任何具有虚拟内容的类都应该是不可复制和不可移动的。如果您需要制作副本,请使用虚拟克隆成语。
  • @n.m.是这样吗?只要尊重B is-an A 恰好具有一些进一步的功能,复制(将B 的实例作为const A&amp; 传递)应该没有问题。它只是忽略除A 之外的B 提供的任何内容,但不影响对象。还是不行?
  • B 的实例作为const A&amp; 传递总是可以的,与复制无关。复制涉及复制构造函数或复制赋值。这些应该是deleted。
  • 你的前提是错误的,父类的公共接口应该永远导致派生类中的任何不一致。这样做违反了 is-a 关系或任何你想称之为的关系。

标签: c++ c++14 move object-slicing


【解决方案1】:

您几乎不想复制移动多态对象。它们通常位于堆上,并通过(智能)指针访问。对于复制,使用虚拟clone 成语;而且几乎没有理由移动它们。因此,如果你的类有一个虚拟析构函数,那么大 5 的其他四个成员应该是 deleted(或者被保护,如果你需要它们来实现你的虚拟 clone)。

但是在(大部分是假设的)情况下,当您确实需要移动一个多态对象,并且您只有一个基指针或引用时,您需要意识到移动也是对象公共接口的一部分。所以它需要让整个对象保持一致的状态,而不仅仅是基础部分。所以你需要确保派生部分知道。做任何事情。通常你会想写一个专用的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      
}; 

现在任何衍生品都可以对从其脚下拉出的基础部分做出适当的反应。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-12
    • 2017-05-30
    • 2012-08-21
    • 2010-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多