Rein virtuelle Funktion in Konstruktor
-
Hallo,
angenommen ich habe zwei Klassen
class B { B() { f(); } virtual void f() = 0; ... }und
class D : public B { D() : B() { // initialisiere irgendwelche data members } void f(); ... }wobei f() irgendwie auf data members von B zugreift.
Wenn ich das so richtig sehe, kann der Konstruktor von D so nicht funktionieren, da zuerst der Konstruktor von B aufgerufen wird, der f aufruft und f greift dann auf data members von D zu, die erst noch initialisiert werden.
Kann mir jemand sagen, ob das so stimmt und, wenn ja, welches der beste Workaround ist?
-
ingobulla schrieb:
Wenn ich das so richtig sehe, kann der Konstruktor von D so nicht funktionieren, da zuerst der Konstruktor von B aufgerufen wird, der f aufruft und f greift dann auf data members von D zu, die erst noch initialisiert werden.
Edit:
@Bashar: "pure virtual" hatte ich übersehen.Zur Ergänzung aber noch eine Sache: Ein Konstruktor "sieht" immer die virtuellen Funktionen, von der aktuellen Klasse bis zu den Basisklassen.
Wenn du eine Hierarchie A->B->C (A ist die unterste Basisklasse) hast, und du ein Element von Typ C anlegst, wird erst der A-Anteil, dann der B-Anteil und zu guterletzt der C-Anteil konstruiert. Das heißt auch das der Konstruktor von B beim Aufruf einer virtuellen Funktion nur A und B betrachtet (Da zu dem Zeitpunkt nur A und B bekannt ist).
ingobulla schrieb:
Kann mir jemand sagen, ob das so stimmt und, wenn ja, welches der beste Workaround ist?
Wenn man nicht die Logik durch überdenken des Konzeptes anpassen kann*, ist der einzige mir auf Anhieb einfallende Weg der, die Initialisierung auf einen späteren Zeitpunkt zu verschieben. In einem solchen Fall gehe ich persönlich dazu über den Konstruktor nicht-public zu machen, sondern über Fabrikmethoden (z.B. eine statische Funktion die ein Objekt erzeugt) zu verwenden. Letztere kann nach dem Konstruktoraufruf noch weitere Initialisierungen durchführen.
* Nachtrag: In der Regel versuche ich die Abhängigkeiten auch bei Vererbung so gering wie möglich zu halten (z.B. in dem man dem Konstruktor der Basisklasse entsprechende Parameter übergibt, und nicht den Standardkonstruktor verwendet; was mit der Verwendung der Initialisierungsliste ja geht).
-
asc schrieb:
ingobulla schrieb:
Wenn ich das so richtig sehe, kann der Konstruktor von D so nicht funktionieren, da zuerst der Konstruktor von B aufgerufen wird, der f aufruft und f greift dann auf data members von D zu, die erst noch initialisiert werden.
Genau.
Falsch, f wird zu diesem Zeitpunkt zu B::f aufgelöst (12.7§3), was dir undefiniertes Verhalten (bzw. einen Programmabruch mit der Meldung "pure virtual function called") einbringt.
-
Bashar schrieb:
Falsch,...
Stimmt, war zu schnell und habe weder das pure-virtual gesehen, noch eine sinnvolle Erklärung geliefert (Oben geändert).
-
Bashar schrieb:
Falsch, f wird zu diesem Zeitpunkt zu B::f aufgelöst (12.7§3), was dir undefiniertes Verhalten (bzw. einen Programmabruch mit der Meldung "pure virtual function called") einbringt.
Auf was bezieht sich denn (12.7§3)?
-
Auf den C++-Standard, ISO/IEC 14882:1998. Abschnitt 12.7, Absatz 3

-
asc schrieb:
Bashar schrieb:
Falsch,...
Stimmt, war zu schnell und habe weder das pure-virtual gesehen, noch eine sinnvolle Erklärung geliefert (Oben geändert).
Das hat mit pure-virtual nix zu tun, in B::B wird niemals D::f aufgerufen werden. Ob B::f dabei pure ist oder nicht spielt keine Rolle.
Wenn es nicht pure wäre, würde es halt keinen Fehler geben, und es würde B::f aufgerufen (was es ja dann geben muss, weil B::f ja implementiert sein muss, wenn es nicht pure ist).
-
asc schrieb:
Wenn man nicht die Logik durch überdenken des Konzeptes anpassen kann, ist der einzige mir auf Anhieb einfallende Weg der, die Initialisierung auf einen späteren Zeitpunkt zu verschieben. In einem solchen Fall gehe ich persönlich dazu über den Konstruktor nicht-public zu machen, sondern über Fabrikmethoden (z.B. eine statische Funktion die ein Objekt erzeugt) zu verwenden. Letztere kann nach dem Konstruktoraufruf noch weitere Initialisierungen durchführen.
Wenn ich das richtig verstehe, wuerde das dann wie folgt aussehen:
class D : public B { D() : B() { // initialisiere irgendwelche data members } void f(); public: static D* create() { D* d = new D(); d->f(); return d; } ... }Ist das so gemeint? Mein Problem hier ist, dass, wenn ich aus B weitere Klassen ableite, die von der Struktur wie D sind, ich in deren Fabrikmethode immer dran denke muss, f() aufzurufen. Daher wuerde ich den Aufruf von f() lieber irgendwie in B behalten.
-
struct BBase { virtual void f (void) = 0; }; template <class C> struct B : BBase { static C* create (void) { std::auto_ptr <C> result (new C); result->f (); return result.release (); } }; struct C : B <C> { void f (void) { ... } C (void) { ...} };
-
ingobulla schrieb:
Ist das so gemeint? Mein Problem hier ist, dass, wenn ich aus B weitere Klassen ableite, die von der Struktur wie D sind, ich in deren Fabrikmethode immer dran denke muss, f() aufzurufen.
Ohne deinen Fall genau zu kennen, kann ich dir nur sagen, das es nur 2 Lösungsansätze gibt:
a) Die Basisklasse über dessen Konstruktor gleich richtig initialisieren (wenn möglich)
b) Über Fabrikmethoden (Und ja, dann kannst du f() auch vergessen)In der Regel würde ich a) ohnehin b) vorziehen.
Edit: Okay, das "Curiously Recurring Template Pattern" habe ich tatsächlich nicht berücksichtigt.
-
audacia schrieb:
struct BBase { virtual void f (void) = 0; }; template <class C> struct B : BBase { static C* create (void) { std::auto_ptr <C> result (new C); result->f (); return result.release (); } }; struct C : B <C> { void f (void) { ... } C (void) { ...} };Das scheint mir die Loesung des Problems. Hat diese Art von Fabrikmethoden-Konstrukt eigentlich einen Namen?
-
ingobulla schrieb:
Das scheint mir die Loesung des Problems. Hat diese Art von Fabrikmethoden-Konstrukt eigentlich einen Namen?
Dieses Templatekonstrukt nennt sich "Curiously Recurring Template Pattern" (Kurios weil es sich auf den Datentyp der Kindklasse bezieht). Dies ist aber unabhängig davon ob man es als Factory oder für andere Dinge verwendet.
-
asc schrieb:
(Kurios weil es sich auf den Datentyp der Kindklasse bezieht).
Wikipedia ist anderer Meinung:
Wikipedia schrieb:
The name of this idiom was coined by Jim Coplien[1], who had observed it in some of the earliest C++ template code.
Ich würde es mit "seltsamerweise immer wieder auftauchendes Template-Pattern" übersetzen.
-
audacia schrieb:
asc schrieb:
(Kurios weil es sich auf den Datentyp der Kindklasse bezieht).
Wikipedia ist anderer Meinung:
Gerade mit dem von dir angegebenen Zitat kann ich keinen Widerspruch zu meiner Beschreibung sehen. Zudem fand ich die Erklärung im "C++ Templates" Buch deutlich besser.
Wikipedia schrieb:
Ich würde es mit "seltsamerweise immer wieder auftauchendes Template-Pattern" übersetzen.
"Kurios wiederholendes Templatepattern" hört sich für mich persönlich besser an.
-
asc schrieb:
Gerade mit dem von dir angegebenen Zitat kann ich keinen Widerspruch zu meiner Beschreibung sehen.
Das Zitat im Kontext des Satzes selbst macht deutlich, daß die Bezeichnung "Curiously Recurring Template Pattern" nicht deskriptiv ist; sie bezieht sich nicht auf das Pattern selbst, sondern auf das Phänomen desselben.
asc schrieb:
Zudem fand ich die Erklärung im "C++ Templates" Buch deutlich besser.
Dann laß mal sehen.
asc schrieb:
"Kurios wiederholendes Templatepattern" hört sich für mich persönlich besser an.
Tatsächlich? Mit meinem Sprachverständnis (und vermutlich auch mit der deutschen Grammatik) ist dieser Satz nicht so recht vereinbar
Was soll, von der Unbekanntheit des englischen Originals ausgehend, "kurios wiederholendes Pattern" denn aussagen? Fehlt bei "wiederholen" nicht ein Objekt?
Übrigens ist LEO bei "couriously" und "recur" recht eindeutig: weder "kurios" noch "wiederholend" sind adäquate Übertragungen.Außerdem finde ich die Bezeichnung "Curiously Recurring Template Pattern" ohnehin meidenswert, da sie nicht deskriptiv ist. Obiges Codebeispiel kann ebensogut als Mixin eingeordnet werden.