Search code examples
c++memorystandards

Does the C++ standard allow for copying an arbitrary polymorphic data structure?


I've searched StackOverflow, but I couldn't find a question that directly addresses this issue.

First some context: I'm trying to implement an Either type in C++ that can handle polymorphic data, much like you can throw a std::runtime_error without the new-keyword. Everything works fine with primitive types, PODs and references, but given that we cannot know the size of a polymorphic data structure upfront, things get more difficult. I then thought about copying the structure to a raw buffer on the heap so that I can pass it around as if it was on the stack.

Example of an Either<L, R>-type:

Either<std::runtime_error, int> doSomeStuff() {
  if (err) {
    return left(std::runtime_error("Not right!"));
  }
  return right(42);
}

I experimented with things like std::memcpy(buf, reinterpret_cast<char*>(static_cast<T*>(&value)), sizeof(T)), but I keep getting SIGSEGV errors. Is this because, as I suspect, polymorphic structures hold extra bookkeeping that becomes corrupt when copying? Is there a way to hold an arbitrary polymorphic structure T on the heap so I can pass it as if it were a normal stack-allocated object? Or is such a thing "undefined" in today's C++ standards?

Update: here's the code I have so far. It's not pretty, but it's the best I've got.

struct ConstBoxRefTag { };
struct BoxMoveTag { };
struct PlainValueTag { };
// struct BoxValueTag { };

template<typename T>
struct GetTag { using type = PlainValueTag; };

template<typename T>
struct GetTag<const Box<T>&> { using type = ConstBoxRefTag; };

template<typename T>
struct GetTag<Box<T>&&> { using type = BoxMoveTag; };

template<typename T>
struct GetTag<Box<T>> { using type = ConstBoxRefTag; };


template<typename T>
class Box<T, typename std::enable_if<std::is_polymorphic<T>::value>::type> {

  void* buf;
  size_t sz;

  template<typename R, typename Enabler>
  friend class Box;

public:

  using Type = T;

  template<typename R>
  Box(R val): Box(typename box::GetTag<R>::type {}, val) {}

  template<typename R>
  Box(ConstBoxRefTag, R oth): buf(std::malloc(oth.sz)), sz(oth.sz) {
    std::memcpy(buf, oth.buf, oth.sz);
  }

  template<typename R>
  Box(BoxMoveTag, R oth): buf(std::move(oth.buf)), sz(std::move(oth.sz)) {
    oth.buf = nullptr;
  };

  template<typename R>
  Box(PlainValueTag, R val): buf(std::malloc(sizeof(R))), sz(sizeof(R)) {
    std::memcpy(buf, reinterpret_cast<void*>(static_cast<T*>(&val)), sizeof(R));
  }

  template<typename R>
  R as() const {
    static_assert(std::is_base_of<T, R>::value, "Class is not a subtype of base class");
    return *static_cast<const R*>(reinterpret_cast<const T*>(&buf));
  }

  T& reference() {
    return *reinterpret_cast<T*>(&buf);
  }

  const T& reference() const {
    return *static_cast<T*>(&buf);
  }

  ~Box() {
    if (buf != nullptr) {
      reference().~T();
      std::free(buf);
    }
  }

};

Solution

  • Indeed, the standard recently added a concept "trivially copyable", such that using memcpy on an object which isn't trivially copyable doesn't result in a valid object. Before "trivially copyable" was introduced, this was controlled by POD-ness.

    To make a copy of a C++ object, you need to call its copy constructor. There's no standard polymorphic way of doing that, but some class hierarchies choose to include a virtual clone() function (or similar) which would meet your need.

    Your other option is to find the way to avoid the copy entirely.