如何防止移动切片?

时间:2019-02-20 16:30:49

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

当派生的类实例作为对不怀疑方法的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(); /**/
  }

这可以解决问题,但是有两个问题,

  • 由于C ++“忘记了” r值函数参数是&&(请参阅标记为std::move的行中对/**/的需要,),很快变得不必要地冗长,
  • 当需要yield编辑多个对象时,很难轻易概括。

是否有更好的/规范的解决方案?也许我缺少真正明显的东西。

1 个答案:

答案 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      
}; 

现在任何派生工具都可以对从脚下拉出的基础部件做出适当的反应。