Wieso keine Polymorphie in ctor?
-
Hi,
class Base { public: Base() {someVirtualStuff();} virtual void someVirtualStuff() = 0; }; class Der : public Base { public: void someVirtualStuff() {std::cout << "Test\n";} }; int main() { Der blub; return 0; }
Das funktioniert nicht, doch wieso? Ich nutze im ctor eine Funktion, die später immer Mal wieder aufgerufen wird und eben someVirtualStuff aufruft. Wenn der ctor diese nicht nutzen darf, muss ich mir einen hässlichen Hack drum herum bauen, nämlich im Der-ctor selbst besagte Methode aufrufen... Finde ich unschön.
-
Weil der Base-Konstruktor vor dem Der-Konstruktor aufgerufen wird. Die Invariante der Der-Klasse ist also noch nicht eingerichtet (sprich: ihre Member sind nicht initialisiert), es darf also keine ihrer Methoden aufgerufen werden. Das läuft in der Praxis so, dass erst der Der-Konstruktor den vptr des Objektes auf Der umbiegt.
-
C++ Konstruktoren verhalten sich nicht Polymorph. Das ist u.a. damit begründet, dass man beim Erstellen einen Objekts ja wissen sollte was man da erstellt.
-
Achso!
Okay, verstanden. Dann rufe ich someVirtualStuff() eben im Der-ctor auf, danke.
-
Ich sage mal, Gott sei Danke, dass es nicht funktioniert. Es ist doch einfach unlogisch, wenn ein Elternteil auf die Gene des Kindes zugreifen könnte. Das widerspricht der Natur.
-
Wenn
Base
konstruiert wird, dann existiert derDer
-Teil des Objekts noch nicht (Konstruktionsreihenfolge!) und der Typ des Objekts ist nochBase
. Daher wird die (hier nicht existente) Funktion aus Base aufgerufen.Was hast du denn vor? Das ist meistens merkwürdiges Design, wenn eine Basisklasse während ihrer Konstruktion wissen muss, zu welchem Objekt sie gehört. Kann aber unter Umständen vorkommen, deswegen schließe ich mal nicht aus, dass du das wirklich brauchst. Guck mal in Effective C++, Item 9 (in der dritten Auflage) nach Erklärungen, Alternativen und Umwegen das gleiche zu erreichen.
-
Gugelmoser schrieb:
Ich sage mal, Gott sei Danke, dass es nicht funktioniert. Es ist doch einfach unlogisch, wenn ein Elternteil auf die Gene des Kindes zugreifen könnte. Das widerspricht der Natur.
Was hat C++ mit Natur zu tun?
-
Erst nachdem das Objekt erstellt wurde, ist die vtbl initialisiert und virtuelle Funktionen koennen benutzt werden. Ein Objekt existiert erst nach der Ausfuehrung des Konstruktors, d.h. im Konstruktor existiert es noch nicht. D.h. koennen nicht vtbl bzw. virtuelle Methoden benutzt werden. Das Verhalten ist undefiniert.
-
hustbaer schrieb:
Gugelmoser schrieb:
Ich sage mal, Gott sei Danke, dass es nicht funktioniert. Es ist doch einfach unlogisch, wenn ein Elternteil auf die Gene des Kindes zugreifen könnte. Das widerspricht der Natur.
Was hat C++ mit Natur zu tun?
Ich verbinde Objektorientierung mit der Natur. Es heißt bestimmt auch nicht zum Spaß Vererbung.
-
Gugelmoser schrieb:
Ich sage mal, Gott sei Danke, dass es nicht funktioniert. Es ist doch einfach unlogisch, wenn ein Elternteil auf die Gene des Kindes zugreifen könnte. Das widerspricht der Natur.
Hier greift überhaupt kein Elternteil auf das Kind zu, umgekehrt: Das Kind schiebt dem Elternteil eine geänderte Methode unter, ohne dass das Elternteil das weiß (bzw. wissen muss). Das ist allgemein üblich, siehe z.B. Template Method Pattern, es klappt halt in C++ nur im Konstruktor nicht.
-
Gugelmoser schrieb:
hustbaer schrieb:
Gugelmoser schrieb:
Ich sage mal, Gott sei Danke, dass es nicht funktioniert. Es ist doch einfach unlogisch, wenn ein Elternteil auf die Gene des Kindes zugreifen könnte. Das widerspricht der Natur.
Was hat C++ mit Natur zu tun?
Ich verbinde Objektorientierung mit der Natur. Es heißt bestimmt auch nicht zum Spaß Vererbung.
Vererbung ist eine is-a-Beziehung, also ein Kind ist ein Eltern ... es ist wirklich unlogisch.
-
Kinder und Eltern sind doch derselbe Typ. Aber eine Amsel "ist ein" Vogel (und ja, ich habe den Meyers gelesen...). Ein Mensch "ist ein" Säugetier. Das weißt du doch sicher selbst.
-
Also in meinem Beispiel macht das deswegen Sinn, weil die Konstruktion des jeweiligen Objektes zu einem großen Teil denselben Algorithmus verwendet. Aber in Teilen des Algorithmuses unterscheidet sich die Konstruktion basierend darauf, welchem Detailtyp die Klasse wirklich entspricht.
Ich möchte eben Template-Method-Pattern bei der Konstruktion anwenden, gleichzeitig halte ich eine Factory oder Factory-Method aber für übertrieben. Im Speziellen geht es um eine QT-GUI-Klasse, die in den meisten Punkten gleich aufgebaut ist, aber für die Spezialfälle in den Spalten einer Tabelle unterschiedliche Elemente hat.
Ich könnte diese natürlich auch nachträglich einfügen und im ctor der Basisklasse erstmal leer lassen... Das würde aber eine zusätzliche Iteration durch die UI-Tabelle erfordern und ein paar Zusatzinformationen muss man dann pro Zeile halt immer sammeln, daher finde ich das wiederum unschön, da man schon Doppelarbeit verursachen würde.
Ich werde es wohl so lösen, dass ich eine initialize()-Methode anbiete, welche die Subklassen dann eben immer aufrufen müssen. Ohne Factory erscheint mir das noch am saubersten. Andere Ideen?
Edit: Ist es auch UB, wenn ich im Der-ctor selbst eine Parent-Methode aufrufe, die wiederum polymorph agiert? Wird der vptr vor dem ctor oder danach erstellt oder ist nur definiert, dass nach dem ctor-Aufruf alles klar ist? Es klappt nämlich gerade, aber das heißt demnach nichts?
-
Gugelmoser schrieb:
hustbaer schrieb:
Gugelmoser schrieb:
Ich sage mal, Gott sei Danke, dass es nicht funktioniert. Es ist doch einfach unlogisch, wenn ein Elternteil auf die Gene des Kindes zugreifen könnte. Das widerspricht der Natur.
Was hat C++ mit Natur zu tun?
Ich verbinde Objektorientierung mit der Natur. Es heißt bestimmt auch nicht zum Spaß Vererbung.
Ganz schwerer Fehler, das führt bloss zu komischen Vorstellungen aus denen schlechter Code erwächst.
-
Keine Idee? Bzw. kann mir jemand das hier beantworten:
Ist es auch UB, wenn ich im Der-ctor selbst eine Parent-Methode aufrufe, die wiederum polymorph agiert? Wird der vptr vor dem ctor oder danach erstellt oder ist nur definiert, dass nach dem ctor-Aufruf alles klar ist? Es klappt nämlich gerade, aber das heißt demnach nichts?
-
Das ist prinzipiell erlaubt, verhält sich aber komisch. Denn jede von Der abgeleitete Klasse Der2 wird sich dann wie ein Der verhalten, sprich Überschreibungen der virtuellen Funktion in Der2 werden ignoriert. Im Prinzip dasselbe Problem wie vorher, nur um eine Stufe der Vererbungshierarchie verlagert. Mit UB hat das IMHO nichts zu tun (lasse mich aber gerne eines besseren belehren, wenn jemand die Standardstelle zitiert). Schau dir mal Meyers an: http://www.artima.com/cppsource/nevercall.html
Mir ist deine Motivation allerdings noch nicht klar. Benutzt du hier Vererbung, um Schreibarbeit zu sparen? Falls ja, dann nix gutes Idee.
-
hustbaer schrieb:
Gugelmoser schrieb:
hustbaer schrieb:
Gugelmoser schrieb:
Ich sage mal, Gott sei Danke, dass es nicht funktioniert. Es ist doch einfach unlogisch, wenn ein Elternteil auf die Gene des Kindes zugreifen könnte. Das widerspricht der Natur.
Was hat C++ mit Natur zu tun?
Ich verbinde Objektorientierung mit der Natur. Es heißt bestimmt auch nicht zum Spaß Vererbung.
Ganz schwerer Fehler, das führt bloss zu komischen Vorstellungen aus denen schlechter Code erwächst.
100% Ack @hustbaer
-
Eisflamme schrieb:
Im Speziellen geht es um eine QT-GUI-Klasse, die in den meisten Punkten gleich aufgebaut ist, aber für die Spezialfälle in den Spalten einer Tabelle unterschiedliche Elemente hat.
Ein GUI-Element definiert sich durch das Aussehen (Existenz und Anordnung bestimmter ELemente) und Verhalten bei Interaktion (durch User oder andere Komponenten).
Bei dir sehen alle Widgets gleich aus und auch das Verhalten wird sich nicht unterscheiden. Einziger Unterschied: andere Daten. Daten sind eigentlich von der GUI getrennt und sollten daher zu keinen neuen Klassen führen.Mein Vorschlag:
Überleg dir ein Interface, mit dem du EINE Klasse auf unterschiedliche Weise initialisieren kannst. Das macht vllt. eine weitere Klasse nötig, evtl. kann HIER mit Vererbung gearbeitet werden (sollte aber nicht zwingend nötig sein, kommt auf deinen speziellen Fall an).
-
Ne, ich spare nicht nur Schreibarbeit. Es sind auch nicht nur andere Daten drin, denn einmal habe ich ein einfaches Label + ProgressBar und das andere Mal ist darin ein Unterwidget enthalten, welches zwei Spalten enthält. Daher ist der Aufbau durchaus anders. Und ich möchte später auch einige grafische Rafinessen einbauen, die sich je Unterklasse unterscheiden.
Bzgl. des Verhaltenscodes will ich ja nicht nur Schreibarbeit sparen, sondern eben auch doppelten Code und doppelte Ausführung von Teilen es Algos vermeiden, das wäre nämlich ebenfalls die Alternative. Wenn sich daran etwas ändert, müsste ich das sonst an mindestens zwei Stellen ändern.
Aber auch das Verhalten der jeweiligen Klassen kann anders sein. Zurzeit ist es noch sehr ähnlich, aber ich sehe da diverse nice-to-have-Anforderungen, die ich bei ausreichend Zeit später noch in Angriff nehmen werde; und dann unterscheiden sich die Klassen ganz erheblich.
Und was das seltsame Verhalten angeht, ist das schon in Ordnung: Wenn jemand die virtuelle Methode überschreibt und dadurch ein anderes Verhalten erzeugt, ist das durchaus sinnvoll, denke ich.
So abstrakt ich das bisher beschrieben habe, hat jemand denn eine andere Idee im Kopf? Eigentlich finde ich das so zurzeit nicht schlecht.
arghonaut:
Also die Verhaltensweisen sind schon anders. Ich hatte vorher ja eine Variante, bei der ich einfach nur anders initialisiert habe. Dann hatte ich aber ganz viele if-Abfragen mit Flags drin, um diverse Schleifen (auch außerhalb des ctors) an verschiedenen Stellen dann doch anders laufen zu lassen. Die Performance spielt hier zwar keine Rolle, aber es sah nicht übersichtlich aus und durch Aufteilung in unterschiedliche Klassen finde ich es schon sauberer als mit Flags rumzuwerfen.
-
Eisflamme schrieb:
Und was das seltsame Verhalten angeht, ist das schon in Ordnung: Wenn jemand die virtuelle Methode überschreibt und dadurch ein anderes Verhalten erzeugt, ist das durchaus sinnvoll, denke ich.
Das funktioniert ja gerade nicht.
Das Design würde ich nochmal überdenken. Hab aber gerade keine Zeit, sodass ich anderen mit mehr GUI-Erfahrung den Vortritt lasse