Подтвердить что ты не робот

Копирование полученных объектов с использованием только указателей базового класса (без исчерпывающего тестирования!) - С++

Учитывая базовый класс, который наследуется множеством производных классов и структурой программы, для которой требуется управлять ими с помощью указателей базового класса для каждого объекта. Есть ли простой способ скопировать весь производный объект, когда известен только указатель базового класса?

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

Еще одно элегантное решение, с которым я столкнулся, выглядит следующим образом:

class Base
{
public:
   Base(const Base& other);
   virtual Base* getCopy();
   ...
}

class Derived :public Base
{
   Derived(const Derived& other);
   virtual Base* getCopy();
   ...
}

Base* Base::getCopy()
{
   return new Base(*this));
}

Base* Derived::getCopy()
{
   return static_cast<Base*>(new Derived(*this));
}

Затем, вызывая getCopy() в указателе Base-класса на любой производный объект, все-таки возвращается указатель базового класса, а также весь экземпляр производного объекта был скопирован. Этот метод намного удобнее обслуживать, поскольку он требует, чтобы аналогичная функция getCopy() находилась во всех производных классах и устраняла необходимость тестирования всех возможных производных объектов.

По существу, это мудрый? или есть лучший, даже более простой способ сделать это?

4b9b3361

Ответ 1

Этот подход является предпочтительным способом копирования полиморфных объектов, поскольку он выгружает ответственность за определение того, как копировать объект произвольного типа на этот объект, а не пытаться определить его во время компиляции. В более общем плане, если вы не знаете, что указатель базового класса указывает во время компиляции, вы не можете знать, какой из многих потенциальных фрагментов кода вам нужно выполнить, чтобы получить правильную копию. Из-за этого для любого рабочего решения потребуется динамический выбор кода, и виртуальная функция - хороший способ сделать это.

Два комментария к вашему фактическому коду. Во-первых, наследование С++ позволяет производному классу переопределять функцию-член базового класса, чтобы производная функция возвращала указатель типа, более специфичного, чем версия базового класса. Это называется ковариацией. В качестве примера, если функция базового класса

virtual Base* clone() const;

Тогда производный класс может переопределить его как

virtual Derived* clone() const;

И это будет отлично. Это позволяет, например, иметь такой код:

Derived* d = // something...
Derived* copy = d->clone();

Что, без ковариантной перегрузки, не будет законным.

Еще одна деталь - в коде у вас есть явно static_cast производные указатели на базовые указатели в вашем коде. Это совершенно законно, но это не обязательно. С++ будет неявно преобразовывать указатели производных классов в указатели базового класса без броска. Если, однако, вы используете идею типа ковариантного возврата, это не произойдет, потому что тип возвращаемого значения будет соответствовать типу объектов, которые вы создадите.

Ответ 2

Обратите внимание, что вам не нужен static_cast. Derived * неявно преобразуется в Base *. Вы абсолютно не должны использовать dynamic_cast для этого, как предлагает Кен Уэйн, поскольку конкретный тип известен во время компиляции, и компилятор может сказать вам, если актер не разрешен.

Что касается подхода, этот шаблон достаточно стандартный, чтобы быть встроенным в С# и Java как ICloneable и Object.clone() соответственно.

Edit:

... или есть лучший, даже более простой способ сделать это?

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

#include <iostream>

struct CloneableBase {
    virtual CloneableBase* clone() const = 0;
};


template<class Derived>
struct Cloneable : CloneableBase {
    virtual CloneableBase* clone() const {
       return new Derived(static_cast<const Derived&>(*this));
    }
};


struct D1 : Cloneable<D1> {
    D1() {}
    D1(const D1& other) {
        std::cout << "Copy constructing D1\n";
    }
};

struct D2 : Cloneable<D2> {
    D2() {}
    D2(const D2& other) {
        std::cout << "Copy constructing D2\n";
    }
};


int main() {
    CloneableBase* a = new D1();
    CloneableBase* b = a->clone();
    CloneableBase* c = new D2();
    CloneableBase* d = c->clone();
}

Ответ 3

Да, твоя идея - путь. Он также позволяет производным классам выбирать, хотите ли они выполнить глубокую или мелкую копию.

У меня есть одна (несколько ничтожная) точка для будущей ссылки: с точки зрения безопасности использование dynamic_cast предпочтительнее static_cast для полиморфных преобразований. Это лишь одна из тех вещей, которые привлекают мое внимание.

Ответ 4

template <class T>
Base* CopyDerived(const T& other) {
  T* derivedptr = new T(other);
  Base* baseptr = dynamic_cast<Base*>(derivedptr);
  if(baseptr != NULL)
    return baseptr;
  delete derivedptr;
  // If this is reached, it is because T is not derived from Base
  // The exception is just an example, handle in whatever way works best
  throw "Invalid types in call to Copy";
}

Для этого требуется только общедоступный конструктор копирования в каждом производном классе, который вы хотите скопировать.