¿Cómo puedo evitar dynamic_cast en mi código C++?
oop inheritance (10)
¿Hay alguna manera de organizar las cosas para que un objeto FooCar pueda llamar a las funciones miembro de FooEngine sin bajar el nivel?
Me gusta esto:
class Car
{
Engine* m_engine;
protected:
Car(Engine* engine)
: m_engine(engine)
{}
};
class FooCar : public Car
{
FooEngine* m_fooEngine;
public:
FooCar(FooEngine* fooEngine)
: base(fooEngine)
, m_fooEngine(fooEngine)
{}
};
Digamos que tengo la siguiente estructura de clase:
class Car;
class FooCar : public Car;
class BarCar : public Car;
class Engine;
class FooEngine : public Engine;
class BarEngine : public Engine;
Demos también un Car
a su Engine
. Se FooCar
un FooCar
con un FooEngine*
y se BarCar
un BarCar
con un BarEngine*
. ¿Hay alguna manera de organizar las cosas para que un objeto FooCar
pueda llamar a las funciones miembro de FooEngine
sin FooEngine
?
Esta es la razón por la cual la estructura de clases se presenta de la manera que es ahora:
- Todos los
Car
tienen unEngine
. Además, unFooCar
solo usaráFooEngine
. - Hay datos y algoritmos compartidos por todos los
Engine
que prefiero no copiar y pegar. - Es posible que desee escribir una función que requiera un
Engine
para saber sobre suCar
.
Tan pronto como dynamic_cast
al escribir este código, supe que probablemente estaba haciendo algo mal. ¿Hay una mejor manera de hacer esto?
ACTUALIZAR:
En base a las respuestas dadas hasta ahora, me inclino por dos posibilidades:
- Have
Car
proporciona una funcióngetEngine()
virtual pura. Eso le permitiría aFooCar
yBarCar
tener implementaciones que devuelvan el tipo correcto deEngine
. - Absorbe toda la funcionalidad del
Engine
en el árbol de herencia delCar
.Engine
se rompió por razones de mantenimiento (para mantener las cosas delEngine
en un lugar separado). Es una compensación entre tener clases más pequeñas (pequeñas en líneas de código) versus tener menos clases grandes.
¿Existe una fuerte preferencia de la comunidad por una de estas soluciones? ¿Hay una tercera opción que no he considerado?
¿Sería posible para el FooCar usar BarEngine?
De lo contrario, es posible que desee utilizar AbstractFactory para crear el objeto de automóvil correcto, con el motor adecuado.
No veo por qué un automóvil no puede estar compuesto por un motor (si BarCar siempre contendrá BarEngine). Engine tiene una relación muy fuerte con el auto. Yo preferiría:
class BarCar:public Car
{
//.....
private:
BarEngine engine;
}
Permitiendo que no me haya perdido algo, esto debería ser bastante trivial.
La mejor manera de hacerlo es crear funciones virtuales puras en Engine, que luego se requieren en las clases derivadas que se van a instanciar.
Una solución de crédito adicional probablemente sería tener una interfaz llamada IEngine, que pasaría a su Auto, y cada función en IEngine es puramente virtual. Podrías tener un ''Motor Base'' que implemente algunas de las funcionalidades que deseas (es decir, ''compartido'') y luego tener una hoja fuera de eso.
La interfaz es agradable si alguna vez tiene algo que quiere ''ver'' como un motor, pero probablemente no lo es (es decir, clases de prueba simuladas y demás).
Puede almacenar el FooEngine en FooCar, BarEngine en BarCar
class Car {
public:
...
virtual Engine* getEngine() = 0;
// maybe add const-variant
};
class FooCar : public Car
{
FooEngine* engine;
public:
FooCar(FooEngine* e) : engine(e) {}
FooEngine* getEngine() { return engine; }
};
// BarCar similarly
El problema con este enfoque es que obtener un motor es una llamada virtual (si le preocupa eso), y un método para configurar un motor en el Car
requeriría una transmisión descendente.
Solo una cosa que quería agregar: este diseño ya me huele mal por lo que llamo árboles paralelos .
Básicamente, si terminas con jerarquías de clases paralelas (como lo tienes con el automóvil y el motor), entonces solo estás buscando problemas.
Me replantearía si Engine (e incluso Car) necesita tener subclases o todas son instancias diferentes de las mismas clases base respectivas.
Creo que depende si Engine
solo es utilizado de forma privada por Car
y sus hijos o si también desea usarlo en otros objetos.
Si las funcionalidades del Engine
no son específicas de Car
s, utilizaría un método virtual Engine* getEngine()
lugar de mantener un puntero en la clase base.
Si su lógica es específica para Car
s, preferiría poner los datos / lógica del Engine
común en un objeto separado (no necesariamente polimórfico) y mantener la implementación de FooEngine
y BarEngine
en su respectiva clase secundaria de Car
.
Cuando el reciclaje de la implementación es más necesario que la herencia de la interfaz, la composición del objeto a menudo ofrece una mayor flexibilidad.
El COM de Microsoft es un poco torpe, pero tiene un concepto novedoso: si tiene un puntero a una interfaz de un objeto, puede consultarlo para ver si admite otras interfaces mediante la función QueryInterface . La idea es dividir tu clase Engine en múltiples interfaces para que cada una se pueda usar de forma independiente.
También puede templatar el tipo de motor de la siguiente manera
template<class EngineType>
class Car
{
protected:
EngineType* getEngine() {return pEngine;}
private:
EngineType* pEngine;
};
class FooCar : public Car<FooEngine>
class BarCar : public Car<BarEngine>
Supongo que Car tiene un puntero de motor, y es por eso que te encuentras abatido.
Saque el puntero de su clase base y reemplácelo con una función virtual get_engine () pura. Entonces, su FooCar y BarCar pueden contener punteros al tipo de motor correcto.
(Editar)
Por qué esto funciona:
Dado que la función virtual Car::get_engine()
devolvería una referencia o un puntero , C ++ permitirá que las clases derivadas implementen esta función con un tipo de devolución diferente , siempre que el tipo de devolución solo difiera por ser un tipo más derivado.
Esto se llama tipos de retorno covariante y permitirá que cada tipo de Car
devuelva el Engine
correcto.