Wie kann ich vermeiden, in meinem C ++ Code dynamic_cast?
-
19-08-2019 - |
Frage
Lassen Sie uns sagen, dass ich die folgende Klassenstruktur:
class Car;
class FooCar : public Car;
class BarCar : public Car;
class Engine;
class FooEngine : public Engine;
class BarEngine : public Engine;
Lassen Sie uns auch ein Car
einen Griff in seine Engine
geben. Ein FooCar
wird mit einem FooEngine*
und einem BarCar
erstellt wird mit einem BarEngine*
erstellt werden. Gibt es eine Möglichkeit, Dinge zu organisieren, so ein FooCar
Objekt Elementfunktionen FooEngine
ohne einziehe anrufen kann?
Hier ist, warum die Klassenstruktur ist es die Art und Weise angelegt ist jetzt:
- Alle
Car
s haben eineEngine
. Des Weiteren wird einFooCar
immer nur eineFooEngine
verwenden. - Es gibt Daten und Algorithmen, die von allen
Engine
s geteilt, die ich lieber nicht kopieren und einfügen. - Ich könnte möchte eine Funktion schreiben, die eine
Engine
erfordert über seineCar
kennen.
Sobald ich dynamic_cast
getippt, wenn Sie diesen Code zu schreiben, wusste ich, dass ich wohl etwas falsch zu machen war. Gibt es einen besseren Weg, dies zu tun?
UPDATE:
Auf der Grundlage der Antworten bisher gegeben, ich bin Neigung in Richtung zwei Möglichkeiten:
- Haben Sie
Car
bieten eine rein virtuellegetEngine()
Funktion. Das würde erlaubenFooCar
undBarCar
Implementierungen zu haben, die die richtige Art vonEngine
zurück. - absorbieren alle die
Engine
Funktionalität in denCar
Vererbungsbaum.Engine
wurde aus Wartungsgründen ausgebrochen (um dieEngine
Sachen in einem separaten Ort). Es ist ein Kompromiss zwischen mehreren kleinen Klassen mit (kleinen in Codezeilen) im Vergleich zu weniger große Klassen mit.
Gibt es eine starke Gemeinschaft Präferenz für eine dieser Lösungen? Gibt es eine dritte Möglichkeit habe ich nicht in Betracht gezogen?
Lösung
Ich gehe davon aus, dass Auto einen Motor Zeiger hält, und das ist, warum Sie finden sich einziehe.
Nehmen Sie den Zeiger aus Ihrer Basisklasse und ersetzen sie durch eine rein virtuelle get_engine () Funktion. Dann wird Ihr FooCar und Barcar können Zeiger auf den richtigen Motortyp halten.
(Edit)
Warum dies funktioniert:
Da die virtuelle Funktion Car::get_engine()
zurückkehren würde eine Referenz oder einen Zeiger , C ++ wird Klassen ermöglichen abgeleitet diese Funktion mit einem verschiedenen Rückgabetyp zu implementieren , solange die Rückkehr Typ unterscheidet sich nur durch eine mehr abgeleiteten Typ zu sein.
Das heißt kovarianten Rückgabetypen , und wird jede Car
Art erlaubt den richtigen Engine
zurückzukehren .
Andere Tipps
Nur eines wollte ich hinzufügen:. Dieser Entwurf schon schlecht riecht mich wegen dem, was ich nenne parallel Bäume
Im Grunde genommen, wenn Sie mit parallelen Klassenhierarchien am Ende (wie man mit Auto und Motor hat), dann sind Sie nur Ärger bringen.
Ich würde überdenken, wenn Engine (und sogar Auto) Subklassen haben muss oder das sind alles nur verschiedene Instanzen der gleichen entsprechenden Basisklassen.
Sie können auch die Motorart templatize wie folgt
template<class EngineType>
class Car
{
protected:
EngineType* getEngine() {return pEngine;}
private:
EngineType* pEngine;
};
class FooCar : public Car<FooEngine>
class BarCar : public Car<BarEngine>
Ich sehe nicht, warum ein Auto nicht von einem Motor zusammengesetzt werden kann (wenn Barcar enthält immer BarEngine). Motor hat eine ziemlich starke Beziehung mit dem Auto. Ich würde es vorziehen:
class BarCar:public Car
{
//.....
private:
BarEngine engine;
}
Wäre es möglich, für die FooCar die BarEngine zu benutzen?
Wenn nicht, mögen Sie vielleicht einen Abstrakte verwenden, um das richtige Auto-Objekt zu erstellen, mit dem richtigen Motor.
Sie können speichern Sie die FooEngine in FooCar, BarEngine in 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
Das Problem bei diesem Ansatz ist, dass ein Motor immer ein virtuelles Call ist (wenn Sie darüber besorgt sind), und ein Verfahren zum Einstellung ein Motor in Car
Downcasting erfordern würde.
Ich denke, es hängt ab, wenn Engine
nur privat von Car
und seinen Kindern oder wenn Sie es auch wollen verwendet wird, in anderen Objekten verwenden.
Wenn Engine
Funktionalitäten nicht spezifisch für Car
s sind, würde ich eine virtual Engine* getEngine()
Methode verwenden, anstatt einen Zeiger in der Basisklasse zu halten.
Wenn seine spezifische Logik Car
s ist, würde ich es vorziehen, die gemeinsame Engine
Daten / Logik in einem separaten Objekt (nicht notwendigerweise polymorphe) und halte FooEngine
und BarEngine
Umsetzung in ihrer jeweiligen Car
Kind Klasse setzen.
Bei der Umsetzung Recycling nötiger als Schnittstelle Vererbung ist, Objekt Zusammensetzung bietet oft mehr Flexibilität.
Microsofts COM ist eine Art klobig, aber es hat ein neuartiges Konzept haben - wenn Sie einen Zeiger auf eine Schnittstelle eines Objekts haben, können Sie es abfragen, um zu sehen, ob es irgendwelche anderen Schnittstellen unterstützt mit der Query-Interface Funktion. Die Idee ist, Ihre Maschine Klasse in mehrere Schnittstellen zu brechen, so dass jeweils unabhängig voneinander verwendet werden können.
Zulassen ich etwas nicht verpasst haben, sollte dies eher trivial sein.
Der beste Weg, dies zu tun, ist reine virtuelle Funktionen in Maschine zu schaffen, die in abgeleiteten Klassen dann erforderlich sind, die instanziiert werden sollen.
Eine zusätzliche Kredit Lösung wäre wahrscheinlich eine Schnittstelle haben genannt iEngine, dass Sie stattdessen Ihr Auto passs, und jede Funktion in iEngine ist rein virtuell. Sie könnten einen ‚BaseEngine‘ haben, dass einige der Funktionen, die Sie wollen (das heißt, ‚geteilt‘) und dann haben Blatt diejenigen aus der das implementiert.
Die Schnittstelle ist schön, sollten Sie immer etwas, das Sie wollen, müssen wie ein Motor zu ‚Look‘, aber wahrscheinlich nicht (das heißt, Mock Testklassen und so weiter).
Gibt es eine Möglichkeit, Dinge zu organisieren, so ein FooCar Objekt Elementfunktionen FooEngine ohne einziehe anrufen kann?
Wie folgt aus:
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)
{}
};