глубокая копия с унаследованными объектами, прикрепленными к указателю - PullRequest
0 голосов
/ 27 сентября 2018

Я не уверен, что лучшее / самое чистое решение для создания глубокой копии объекта с умным указателем, содержащим унаследованные объекты.Чтобы свести его к минимуму, с помощью следующего кода

class A {};

class D1 : public A{
public:
    int x1 = 0;
};

class D2 : public A {
public:
    int x2 = 2;
};


class V {
public:
    V(A* a) : ptr(a) {}
    std::unique_ptr<A> ptr;
};    

void run() {
    std::vector<V> v;
    v.push_back(V(new D1));
    v.push_back(V(new D2));
    /// I want to make a deep copy of v here
}

, где вектор v содержит оба объекта типа D1 и D2, что является самым коротким / самым элегантным способом сделать глубокую копиюv?Я могу придумать два пути, оба с некоторыми недостатками:

  1. создать виртуальный A* clone() метод в базовом классе и перегрузить его в каждом унаследованном классе (как описано здесь ),Недостаток: метод clone должен быть создан в каждом унаследованном классе, и их может быть несколько.
  2. создать конструктор копирования / оператор присваивания для V.Используя dynamic_cast<D1/D2>, проверьте, какой тип наследуемого объекта прикреплен, и сделайте копию для этого конкретного типа.Недостаток: нужно пройти через все унаследованные классы в конструкторе копирования V.

Ответы [ 2 ]

0 голосов
/ 27 сентября 2018

Хорошо, давайте посмотрим на это:

  1. A не имеет виртуального dtor, поэтому dtor V вызывает UB, когда member- unique_ptr пытаетсядля полиморфного уничтожения ist pointee.

  2. dynamic_cast можно использовать только для проверки наиболее производного типа, если он действительно final, а тип-источник имеет виртуальные методы и /или базы.Хотя вы, кажется, не происходят от D1 и / или D2, ничто не мешает кому-либо еще делать это.И у вас нет никаких виртуальных баз или методов.По крайней мере, вместо этого используйте typeid и добавьте виртуальный dtor.

  3. Использование виртуального .clone() позволяет вам исключить все утомительные и подверженные ошибкам проверки типов и содержит любые необходимые изменения.при расширении до нового класса.В качестве альтернативы можно зарегистрировать его с помощью карты, сохранить указатель на метод-клон или записать все это в виде кода.

0 голосов
/ 27 сентября 2018

Вариант 1 не потребует от вас изменения V каждый раз, когда класс добавляется в иерархию в A.Кроме того, если добавленный класс не реализует clone, вы получите хорошую блестящую ошибку компилятора вместо того, чтобы все создавалось и терпело неудачу во время выполнения, как в варианте 2.

Так вариант 1лучше.Но вы правы, это несколько повторяется.Вы должны написать похожий код для множества разных типов.К счастью, в C ++ есть механизм для этого: шаблоны.

Используя класс CRTP , мы можем автоматически реализовать функцию clone.Все, что нужно D1 и D2, - это наследовать от посредника, а не от A напрямую:

class A {
public:
  virtual A* clone() const = 0;
  virtual ~A() = default;
};

template<class C>
struct AClone : A {
  A* clone() const override {
    return new C(*static_cast<C const*>(this));
  }
};

class D1 : public AClone<D1> {
public:
    int x1 = 0;
};

class D2 : public AClone<D2> {
public:
    int x2 = 2;
};

Выше приведено использование необработанных указателей, и, вероятно, оно будет улучшеновернув вместо этого unique_ptr, но эта идея сводится к краткости.

Можно также добавить немного защитного программирования к этой функции clone.

static_assert(std::is_convertible<C*, A*>::value,"");
static_assert(std::is_convertible<C*, AClone*>::value,"");
// These two check `C` is derived unambiguasly from `A` via this specialization

assert(typeid(C) == typeid(*this));
// Check the most derived type is as expected, suggested by Deduplicator

И выможно посмотреть вживую, здесь .

...