Позволяет ли стандарт C ++ копировать произвольную полиморфную структуру данных? - PullRequest
1 голос
/ 20 апреля 2019

Я искал StackOverflow, но не смог найти вопрос, который напрямую решает эту проблему.

Сначала немного контекста: я пытаюсь реализовать тип Either в C ++, который может обрабатывать полиморфизмданные, так же, как вы можете бросить std::runtime_error без new ключа.Все отлично работает с примитивными типами, POD и ссылками, но, учитывая, что мы не можем заранее знать размер полиморфной структуры данных, все становится сложнее.Затем я подумал о копировании структуры в необработанный буфер в куче, чтобы я мог передать ее вокруг , как если бы это было в стеке.

Пример Either<L, R> -типа:

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

Я экспериментировал с такими вещами, как std::memcpy(buf, reinterpret_cast<char*>(static_cast<T*>(&value)), sizeof(T)), но продолжаю получать ошибки SIGSEGV.Это потому, что, как я подозреваю, полиморфные структуры содержат дополнительную бухгалтерию, которая становится поврежденной при копировании?Есть ли способ хранить произвольную полиморфную структуру T в куче, чтобы я мог передать ее, как если бы это был обычный объект, выделенный стеком?Или в современных стандартах C ++ такая вещь «неопределена»?

Обновление: вот код, который у меня есть.Это не красиво, но это лучшее, что у меня есть.

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

};

1 Ответ

1 голос
/ 20 апреля 2019

Действительно, в стандарт недавно добавлена ​​концепция «тривиально копируемого», такая, что использование memcpy для объекта, который не является тривиально копируемым, не приводит к допустимому объекту. До того, как было введено «тривиально копируемое», это контролировалось POD-ness.

Чтобы сделать копию объекта C ++, вам нужно вызвать его конструктор копирования. Не существует стандартного полиморфного способа сделать это, но некоторые иерархии классов предпочитают включать виртуальную функцию clone() (или аналогичную), которая отвечала бы вашим потребностям.

Другой вариант - найти способ полностью избежать копирования.

...