Pregunta Copiando entidades derivadas utilizando solo punteros de clase base (¡sin pruebas exhaustivas!) - C ++


Dada una clase base que es heredada por una plétora de clases derivadas, y una estructura de programa que requiere que administres estos a través de punteros de clase base para cada entidad. ¿Existe una forma simple de copiar todo el objeto derivado cuando solo se conoce el puntero de la clase base?

Mirando a su alrededor parece posible (si es increíblemente tedioso) usar el dynamic_cast llame para verificar si un puntero base se puede convertir como la clase derivada apropiada, y luego cópielo usando el constructor de copia de la clase derivada. Sin embargo, esta no es realmente una solución óptima, en parte debido al uso excesivo de dynamic_cast y también vería un dolor de cabeza total para mantener y extender.

Otra solución más elegante que he encontrado es la siguiente:

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

Luego llamando getCopy() en el puntero de la clase Base a cualquier objeto derivado, uno obtiene un puntero de clase base pero también se ha copiado todo el objeto derivado. Este método se siente mucho más fácil de mantener, ya que solo requiere una similar getCopy() función para estar en todas las clases derivadas, y elimina la necesidad de probar contra todos los posibles objetos derivados.

Esencialmente, ¿es esto sabio? o hay una forma mejor, incluso más ordenada de hacer esto?


32
2018-02-17 10:03


origen


Respuestas:


Este enfoque es la forma preferida de copiar objetos polimórficos porque descarga la responsabilidad de determinar cómo copiar un objeto de un tipo arbitrario a ese objeto, en lugar de intentar determinarlo en tiempo de compilación. De manera más general, si no sabe a qué apunta el puntero de la clase base en tiempo de compilación, posiblemente no pueda saber cuál de las muchas piezas potenciales de código necesitará ejecutar para obtener una copia correcta. Debido a esto, cualquier solución que funcione necesitará una selección dinámica de código, y la función virtual es una buena forma de hacerlo.

Dos comentarios sobre tu código real. En primer lugar, la herencia de C ++ permite que una clase derivada anule una función de miembro de clase base para que la función derivada devuelva un puntero de un tipo más específico que la versión de clase base. Esto se llama covarianza. Como ejemplo, si una función de clase base es

virtual Base* clone() const;

Entonces una clase derivada puede anularlo como

virtual Derived* clone() const;

Y esto funcionará perfectamente bien. Esto le permite, por ejemplo, tener un código como este:

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

Lo cual, sin la sobrecarga covariante, no sería legal.

Otro detalle: en el código que tienes, explícitamente static_cast los punteros derivados a punteros base en su código. Esto es perfectamente legal, pero no es necesario. C ++ convertirá implícitamente punteros de clase derivados a punteros de clase base sin conversión. Sin embargo, si usa la idea del tipo de retorno covariante, esto no aparecerá porque el tipo de retorno coincidirá con el tipo de los objetos que creará.


33
2018-02-17 10:07



Tenga en cuenta que no necesita el static_cast allí. Derivado * convierte a Base * implícitamente. No debe utilizar un dynamic_cast para eso, como sugiere Ken Wayne, ya que el tipo concreto se conoce en tiempo de compilación, y el compilador puede decirle si el elenco no está permitido.

En cuanto al enfoque, este patrón es lo suficientemente estándar para integrarse a C # y Java como ICloneable y Object.clone (), respectivamente.

Editar:

... o hay una forma mejor, incluso más ordenada de hacer esto?

Podría usar una "clase base auto-parametrizada", que le ahorra la implementación de la función clone () cada vez. Solo necesita implementar el constructor de copia:

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

2
2018-02-17 10:20



Sí, tu idea es el camino a seguir. También permite que las clases derivadas elijan si desean realizar una copia profunda o superficial.

Tengo un punto (un tanto quisquilloso) para futuras referencias: en términos de seguridad, el uso de dynamic_cast es preferible a static_cast para las conversiones polimórficas. Es solo una de esas cosas que llama mi atención.


0
2018-02-17 10:12



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

Esto solo requiere un constructor de copia accesible al público en cada clase derivada que desea copiar.


0
2018-05-05 17:56