Initialisierungen bei Vererbung
-
Hey!
Einfach abgeleitete Klassen initialisiere ich so:
class A { int a; public: A(int a_) : a(a_) { std::cout << "A Constructor " << a << std::endl; } }; class B : public A { int b; public: B(int a_, int b_) : A(a_), b(b_) { std::cout << "B Constructor " << b << std::endl; } }; class C : public B { int c; public: C(int a_, int b_, int c_) : B(a_, b_), c(c_) { std::cout << "C Constructor " << c << std::endl; } };
Ist doch so richtig, oder? Von jeder Klasse kann nun ein Objekt erzeugt werden, dabei wird nie ein Default-Konstruktor benötigt, sondern, egal ob direkte oder indirekte Basisklasse, immer geeignet initialisiert.
Wie sieht das nun aber bei Mehrfachvererbung aus?
class A { int a; public: A(int a_) : a(a_) { std::cout << "A Constructor " << a << std::endl; } }; class B : virtual public A { int b; public: B(int a_, int b_) : A(a_), b(b_) { std::cout << "B Constructor " << b << std::endl; } }; class C : virtual public A { int c; public: C(int a_, int c_) : A(a_), c(c_) { std::cout << "C Constructor " << c << std::endl; } }; class D : public B, public C { int d; public: D(int a_, int b_, int c_, int d_) : A(a_), B(a_, b_), C(a_, c_), d(d_) { std::cout << "D Constructor " << d << std::endl; } };
Wenn ich ein Objekt von D erzeugen möchte, wird hier unnötig der Konstruktor von B und C mit a_ aufgerufen. Dies hat sowieso keinen Effekt, da für die abgeleitete Klasse D mit indirekter virtueller Basisklasse die Konstruktoren von B und C als Basisinitialisierung ignoriert werden.
Also muss ich nun jeweils einen zweiten geeigneten Konstruktor schreiben? Oder wie macht man das normalerweise/am besten?
Danke!
MfG
-
class D : public B, public C { int d; public: D(int a_, int b_, int c_, int d_) : B(a_, b_), C(a_, c_) { std::cout << "D Constructor " << d << std::endl; } };
sollte doch ausreichen ...
-
ceplusplus@loggedoff schrieb:
Also muss ich nun jeweils einen zweiten geeigneten Konstruktor schreiben? Oder wie macht man das normalerweise/am besten?
Ein zweiter Konstruktor ist sicherlich eine Möglichkeit. Bei komplexeren Argumenten kämen Referenzen in Betracht, generell ist das sowieso nur eine Variante des Forwardingproblems. In der Praxis dürfte das aber selten ein echtes Problem sein - den Konstruktoren von B und C Argumente für die Initialisierung von A mitzugeben impliziert ja, dass B und C nicht abstrakt sind, und das dürfte selten eine korrekte Designentscheidung sein.
-
@(D)Evil
Das geht nicht:
"Der Konstruktor einer virtuellen Basisklasse wird mit den Argumenten aufgerufen, die im Basisinitialisierer der zuletzt abgeleiteten Klasse angegeben sind"Bei deinem Beispiel müsste Klasse A einen Default-Konstruktor besitzen und würde nicht sofort geeignet initialisiert werden.
camper schrieb:
den Konstruktoren von B und C Argumente für die Initialisierung von A mitzugeben impliziert ja, dass B und C nicht abstrakt sind, und das dürfte selten eine korrekte Designentscheidung sein.
Hm? Von B und C sollen doch genauso Objekte angelegt und verwendet werden können, es sind immerhin schon spezielle Objekte von A.
Beispiel:
Klasse A = Roboter
Klasse B = FahrenderRoboter
Klasse C = LaufenderRoboter
Klasse C = MultifunktionalRoboterWenn ich nun einen fahrenden Roboter erstellen möchte, muss auch ein geeigneter Konstruktor für Roboter vorhanden sein...
MfG
-
ceplusplus@loggedoff schrieb:
@(D)Evil
Das geht nicht:
"Der Konstruktor einer virtuellen Basisklasse wird mit den Argumenten aufgerufen, die im Basisinitialisierer der zuletzt abgeleiteten Klasse angegeben sind"Bei deinem Beispiel müsste Klasse A einen Default-Konstruktor besitzen und würde nicht sofort geeignet initialisiert werden.
Afaik braucht er das nicht - jedes (Teil)Objekt wird genau einmal konstruiert - und der A-Anteil von D direkt aus dem D-Ctor heraus. D.h., B und C können in jedem Fall davon ausgehen, mit einem funktionstüchtigen A arbeiten zu können (selbst wenn dessen Inhalt nicht zu den eigenen Vorstellungen passen sollte).
-
ceplusplus@loggedoff schrieb:
Von B und C sollen doch genauso Objekte angelegt und verwendet werden können, es sind immerhin schon spezielle Objekte von A.
Beispiel:
Klasse A = Roboter
Klasse B = FahrenderRoboter
Klasse C = LaufenderRoboter
Klasse C = MultifunktionalRoboterWenn ich nun einen fahrenden Roboter erstellen möchte, muss auch ein geeigneter Konstruktor für Roboter vorhanden sein...
MfG
-
ceplusplus@loggedoff schrieb:
ceplusplus@loggedoff schrieb:
Von B und C sollen doch genauso Objekte angelegt und verwendet werden können, es sind immerhin schon spezielle Objekte von A.
Beispiel:
Klasse A = Roboter
Klasse B = FahrenderRoboter
Klasse C = LaufenderRoboter
Klasse C = MultifunktionalRoboterWenn ich nun einen fahrenden Roboter erstellen möchte, muss auch ein geeigneter Konstruktor für Roboter vorhanden sein...
MfG
Damit wird das LSP verletzt. Ein FahrenderRoboter läuft niemals (nehm ich jetzt mal an), ein LaufenderRoboter fährt niemals, ein MultifunktionalRoboter, der laufen und fahren kann ist demzufolge weder ein FahrenderRoboter noch ein LaufenderRoboter.
-
Dann muss man die Klassen halt umnennen:
Roboter RoboterWoUnterAnderemFahrenKann : Roboter RoboterWoUnterAnderemLaufenKann : Roboter RoboterWoLaufenUndFahrenUndVielleichtNochwasAnderesKann : RoboterWoUnterAnderemFahrenKann, RoboterWoUnterAnderemLaufenKann
-
Mhm achso verstehe, der MultifunktionalRoboter IST KEIN LaufenderRoboter ODER FahrenderRoboter.
Hmm, kennt jemand ein anderes Beispiel?
Wie wärs mit...
KFZ / \ PKW TRANSPORTER \ / KOMBI
Kombi IST ein PWK und Kombi IST ein Transporter? Oder nicht?
MfG
-
Ne ich verstehs doch ned...
Das Liskov-Prinzip besagt doch, dass das Verhalten der abgeleiteten Klasse identisch mit dem der Basisklasse sein muss.
Das ist es in meinem obigen Beispiel doch auch. FahrenderRoboter fährt. MultifunktionalRoboter fährt genauso. LaufenderRoboter läuft. MultifunktionalRoboter läuft genauso.
Habe auch gelesen, dass Mehrfachvererbung selten sinnvoll ist :-| Stimmt das?
MfG
-
ceplusplus@loggedoff schrieb:
Mhm achso verstehe, der MultifunktionalRoboter IST KEIN LaufenderRoboter ODER FahrenderRoboter.
Hmm, kennt jemand ein anderes Beispiel?
Wie wärs mit...
KFZ / \ PKW TRANSPORTER \ / KOMBI
Kombi IST ein PWK und Kombi IST ein Transporter? Oder nicht?
MfG
Ja und nein. Das Problem ist immer eines der Konkretisierung - in der natürlichen Sprache verwenden wir oft das gleiche Wort für eine abstrakte Kategorie oder etwas Konkreteres (und den Rest schließen wir aus dem Kontext). An deinem Beispiel ist zunächst nichts falsch - das kann erst passieren, wenn du Fehler beim Erstellen von Objekten machst (nur Objekte können sich polymorph verhalten). Ein PKW (das heißt, ein Objekt, dass direkt als PKW instantiiert wird) kann nur Personen transportieren, ein Transporter (...) nur irgendwelche Sachen (in unserem Programm - wenn beides könnten, selbst nur in unterschiedlichem Grade, gäbe es ja keinen Grund, den Unterschied in verschiedene Klassen zu gießen). Ein PKW ist demzufolge leer (und kann Personen aufnehmen), wenn keine Personen drin sind. Ein Transporter ist leer, wenn keine Sachen drin sind (und kann dann beladen werden). Ein Kombi ist nicht unbedingt leer nur weil er keine Personen oder keine Sachen enthält - und ist folglich weder ein PKW noch ein Transporter. Diese Argumentation ist zutreffend, wenn PKW,Transporter und Kombi gleichermaßen konkrete Klassen darstellen von denen vollständige Objekte erstellt werden können. Etwas anderes wäre es, wenn wir hier in diesem Beispiel PKW und Transporter sowohl als Konzept als auch als konkrete Beschreibung eines Transportmittels ansehen:
KFZ / \ PKW TRANSPORTER / \ / \ nurPKW KOMBI nurTRANSPORTER
In diesem Falle beschreibt PKW nur die Fähigkeit, Personen transportieren zu können - und KOMBI hat diese Fähigkeit auch - aber die Eigenschaft, dass das Fahrzeug leer ist, wenn keine Personen mehr drin sind, kann nicht mehr daraus abgeleitet werden, dass es sich um einen PKW handelt. Nur für nurPKW trifft dies zu. Generell muss eine LSP-Vererbungshierarchie immer vom Abstrakteren zum Konkreteren hin ableiten. Eine Kindklasse, die nicht konkreter als ihre Elternklasse ist, ist entweder mit dieser identisch (dann gibt es keinen Grund, überhaupt erst abzuleiten), oder sie verletzt das LSP. Die gleiche Argumentation kannst du prinzipiell auf jedes andere Beispiel anwenden. Als Grundregel, die wirklich so gut wie keine Ausnahmen hat: Leite niemals von einer Klasse ab, die auch vollständige Objekte erzeugen soll. Verletzungen dieser Regel vermischen Abstraktionsebenen und das ist ein ganz schneller Weg ins Verderben.
Um zum Ausgangspunkt dieses Threads zurückzukommen: die Klassen B,C sind in diesem Schema keine Blätter des Vererbungsbaumes mehr: da sie nie konkrete Objekte erzeugen (sollen - ob sie abstrakt im Sinne der Sprache sind, ist unerheblich) müssen ihre Konstruktoren evtl. vorhandene Basisklassen nie selbst initialisieren und es ist folglich nicht notwendig, sie mit entsprechenden Argumenten zu füttern.P.S. Meine Vererbunghierarchie ist als solche nicht wirklich brauchbar - sie dient nur zur Illustration der Argumentation.
-
Danke für die Erklärung. Ich glaub jetzt hab ichs.
Man sollte sich also immer zuerst Gedanken über ALLE Eigenschaften jedes Hierarchieknotens machen...MfG