Como posso evitar dynamic_cast no meu código C ++?
-
19-08-2019 - |
Pergunta
Vamos dizer que eu tenho a seguinte estrutura de classe:
class Car;
class FooCar : public Car;
class BarCar : public Car;
class Engine;
class FooEngine : public Engine;
class BarEngine : public Engine;
Let também é dar um Car
um identificador para a sua Engine
. A FooCar
será criado com um FooEngine*
e uma BarCar
será criado com um BarEngine*
. Existe uma maneira de organizar as coisas de modo um objeto FooCar
pode chamar funções de membro de FooEngine
sem downcasting?
É aqui porque a estrutura de classe é colocado para fora do jeito que está agora:
- Todos
Car
s ter umEngine
. Além disso, umFooCar
só vai usar umaFooEngine
. - Existem dados e algoritmos compartilhados por todos os
Engine
s que eu preferiria não copiar e colar. - eu poderia querer escrever uma função que requer uma
Engine
de saber sobre a suaCar
.
Assim que eu digitei dynamic_cast
ao escrever este código, eu sabia que provavelmente estava fazendo algo errado. Existe uma maneira melhor de fazer isso?
UPDATE:
Com base nas respostas dadas até agora, eu estou inclinado para duas possibilidades:
- Tem
Car
fornecer uma funçãogetEngine()
virtual pura. Isso permitiriaFooCar
eBarCar
ter implementações que retornam o tipo correto deEngine
. - absorver toda a funcionalidade
Engine
na árvore de herançaCar
.Engine
foi quebrado por razões de manutenção (para manter as coisasEngine
em um lugar separado). É um trade-off entre ter mais classes pequenas (pequena em linhas de código) versus tendo menos classes grandes.
Existe uma forte preferência comunidade para uma dessas soluções? Existe uma terceira opção que eu não tenha considerado?
Solução
Estou assumindo que o carro possui um ponteiro Motor, e é por isso que você se encontra downcasting.
Leve o ponteiro fora de sua classe base e substituí-lo com uma função pura get_engine virtual (). Em seguida, seu FooCar e BarCar pode conter ponteiros para o tipo de motor correto.
(Edit)
por que isso funciona:
Desde o Car::get_engine()
função virtual retornaria um
Isso é chamado covariant tipos de retorno , e permitirá que cada tipo Car
para retornar o Engine
correta .
Outras dicas
Apenas uma coisa que eu queria acrescentar:. Este projeto já cheira mal para mim por causa do que eu chamo de árvores paralelas
Basicamente, se você acabar com hierarquias de classe paralelas (como você tem com carro e do motor), então você está apenas pedindo para ter problemas.
Gostaria de repensar se Engine (e até mesmo carro) precisa ter subclasses ou aqueles são todos apenas diferentes instâncias das mesmas classes respectiva base.
Você também pode templatize a Motor da seguinte maneira
template<class EngineType>
class Car
{
protected:
EngineType* getEngine() {return pEngine;}
private:
EngineType* pEngine;
};
class FooCar : public Car<FooEngine>
class BarCar : public Car<BarEngine>
Eu não vejo por que um carro não pode ser composto por um motor (se BarCar sempre conterá BarEngine). Motor tem um muito forte relacionamento com o carro. Eu preferiria:
class BarCar:public Car
{
//.....
private:
BarEngine engine;
}
Seria possível para o FooCar usar o BarEngine?
Se não, você pode querer usar um AbstractFactory para criar o objeto direito carro, com o motor direito.
Você pode armazenar o FooEngine em FooCar, BarEngine em 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
O problema com esta abordagem é que a obtenção de um motor é uma chamada virtual (se você está preocupado com isso), e um método para definição um motor em Car
exigiria downcasting.
Eu acho que isso depende se Engine
só é usada em particular por Car
e seus filhos ou se você também quiser usá-lo em outros objetos.
Se funcionalidades Engine
não são específicos para Car
s, eu usaria um método virtual Engine* getEngine()
em vez de manter um ponteiro na classe base.
Se a sua lógica é específico para Car
s, eu preferiria colocar os dados comum Engine
/ lógicas em um objeto separado (não necessariamente polimórfico) e manter FooEngine
e implementação BarEngine
em sua respectiva classe Car
criança.
Quando a reciclagem implementação é mais necessária do que a herança interface, composição de objetos muitas vezes oferecem maior flexibilidade.
COM da Microsoft é uma espécie de desajeitado, mas ele tem um conceito novo - se você tem um ponteiro para uma interface de um objeto, você pode consultá-lo para ver se ele suporta quaisquer outras interfaces usando o função QueryInterface . A idéia é quebrar sua classe Motor em várias interfaces de modo que cada um pode ser usado de forma independente.
Permitir que eu não tenha perdido alguma coisa, isso deve ser bastante trivial.
A melhor maneira de fazer isso é criar funções virtuais puros em motores, que são então necessários em classes derivadas que estão a ser instanciado.
Uma solução crédito extra seria provavelmente ter uma interface chamada IEngine, que em vez passs para o seu carro, e cada função em IEngine é puro virtual. Você poderia ter um 'BaseEngine' que implementa algumas das funcionalidades que você quer (ou seja, 'partilhada') e depois ter as folhas fora dessa.
A interface é agradável que você nunca deve ter algo que você quer 'olhar' como um motor, mas provavelmente não é (ou seja, classes de teste de simulação e tal).
Existe uma maneira de organizar as coisas de modo um objeto FooCar pode chamar funções de membro de FooEngine sem downcasting?
Como esta:
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)
{}
};