Search code examples
c++c++14moveobject-slicing

How to prevent move slicing?


When a derived class instance is passed as a r-value parent reference to an unsuspecting method, the latter can legally change the parent's contents, causing incoherence with any extra data stored in the actual object. Therefore a class designed for extension can not rely on default move semantics. Consider for a trivial example:

#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!
}

In order to dispatch the resource hand-over to the most derived class, I thought of using a virtual function to get the moved-from resource in the move constructor:

... 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(); /**/
  }

This does the trick but has two issues,

  • gets unnecessarily verbose rather quickly due to C++ "forgetting" that a r-value function parameter was a && (see the need for std::move in lines marked /**/),
  • can't be generalized easily when more than one object needs to be yield'ed.

Is there a better / a canonical solution? Maybe I'm missing something really obvious.


Solution

  • You almost never want to copy or move polymorphic objects. They generally live on the heap, and are accessed via (smart) pointers. For copying, use the virtual clone idiom; and there's almost never a reason to move them. So if your class has a virtual destructor, the other four members of the big 5 should be deleted (or made protected, if you need them to implement your virtual clone).

    But in a (mostly hypothetical) situation when you do need to move a polymorphic object, and you only have a base pointer or reference, you need to realise that moving-from is also a part of the object's public interface. So it needs to leave the entire object in a consistent state, not just the base part. So you need to make sure the derived parts know. Do whatever it takes. Normally you would want to write a dedicated move-from virtual function, and call it in your move constructor/assignment:

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

    Now any derived can properly react to the base part being pulled from under its feet.