С++: глубокое копирование указателя базового класса

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

class Base {}

Затем длинная строка производных классов:

class Derived_1:: public Base {}
class Derived_2:: public Derived_1{}
...
...
class Derived_n:: public Derived_M{}

И у меня есть еще один класс:

class DeepCopy 
{ 
  Base * basePtr;

  public:
   DeepCopy(DeepCopy & dc) {}
}

Предполагая, что конструкторы копирования класса Base и Derived_x правильно закодированы, как лучше всего написать конструктор копирования для DeepCopy. Как мы можем узнать о классе, который находится в basePtr объекта, который мы собираемся скопировать?

Единственный способ, которым я могу думать, - использовать RTTI, но использование длинного списка dynamic_casts кажется неправильным. Кроме того, DeepCopy требует знать иерархию наследования базового класса.

Другой метод, который я видел, здесь. Но для этого требуется, чтобы классы Base и Derived реализовали метод clone.

Итак, есть гораздо более простой, стандартный способ сделать это?

Ответы

Ответ 1

Вам нужно использовать шаблон виртуальной копии: предоставить виртуальную функцию в интерфейсе, который выполняет копию, а затем реализовать ее по иерархии:

struct base {
   virtual ~base() {}                // Remember to provide a virtual destructor
   virtual base* clone() const = 0;
};
struct derived : base {
   virtual derived* clone() const {
      return new derived(*this);
   }
};

Затем объект DeepCopy должен просто вызвать эту функцию:

class DeepCopy 
{ 
  Base * basePtr;    
public:
   DeepCopy(DeepCopy const & dc)           // This should be `const`
      : basePtr( dc.basePtr->clone() )
   {}
};

Ответ 2

Использование подхода, использующего функцию clone(), является хорошим решением. Обратите внимание, что с помощью CRTP (любопытно повторяющийся шаблон шаблона) может сэкономить вам часть работы. То, как вы это делаете, - это ввести промежуточный уровень (называемый BaseCRTP ниже), который является шаблоном и реализует функцию clone(). Когда вы получаете свои фактические классы, используйте их как аргумент шаблона базы, из которой они получены. Они получат функцию clone(), реализованную для них автоматически. Убедитесь, что производные классы реализуют конструктор копирования (или убедитесь, что по умолчанию это то, что вам нужно).

/* Base class includes pure virtual clone function */
class Base {
public:
  virtual ~Base() {}
  virtual Base *clone() const = 0;
};

/* Intermediate class that implements CRTP. Use this
 * as a base class for any derived class that you want
 * to have a clone function.
 */
template <typename Derived>
class BaseCRTP : public Base {
public:
  virtual Base *clone() const {
      return new Derived(static_cast<Derived const&>(*this));
  }
};

/* Derive further classes. Each of them must
 * implement a correct copy constructor, because
 * that is used by the clone() function automatically.
 */
class Derived1 : public BaseCRTP<Derived1> {
  /*... should have an ordinary copy constructor... */
};

class Derived2 : public BaseCRTP<Derived2> {
  /*... should have an ordinary copy constructor... */
};

Вы можете, очевидно, реализовать класс DeepCopy обычным способом:

class DeepCopy 
{ 
  Base *basePtr;    
public:
  DeepCopy(const DeepCopy &dc)
    : basePtr(dc.basePtr->clone())
  {}
};

Ответ 3

Я думаю, что шаблоны - лучший способ пойти в этой ситуации:

template<typename Sub>
class DeepCopy
{
    Base *base;

    DeepCopy(Sub *sub)
    {
        base = new Sub(*sub); // use copy constructor
    }
}

Это означает, что DeepCopy не назначаются друг другу, но цена, которую вы платите с помощью С++.