Aufruf einer "pure virtual" Methode über Basisklassen-Konstruktor?



  • Hi,

    wahrscheinlich kam das schon 100x, aber trotzdem... folgendes Problem:

    Ich hab eine Basisklasse

    class A
    {
    public:

    A(int dummy);

    protected:

    virtual void init()=0;
    };

    Dazu mehrere abgeleitete Klassen der Form:

    class Asub : public A
    {
    public:
    Asub(int dummy) : A(dummy) {;}
    protected:
    virtual void init();
    };

    init() soll nun in jeder sub-Klasse etwas anderes tun, ABER automatisch vom A-Konstruktor aufgerufen werden, also ohne dass ich djedesmal im Konstruktor Asub() init() aufrufen muss.

    Gibt es da irgendein besonders kluges Rezept für?



  • Nein, gibt es nicht - während du im A-Konstruktor bist, existiert der Asub-Teil deines Objekts noch nicht (inklusive Virtual Table und Membern, die die init()-Methode nutzen könnte).

    Einzige Lösung wäre es, wenn du die Initialisierung direkt in die beteiligten Konstruktoren packst.



  • ok, danke!

    ist eigentlich auch logisch, aber ich dachte dass das die vtable hinkriegt weil doch eigentlich schon bei der übersetzung klar ist, was getan werden soll...

    aber vielleicht mach ich da auch grad einen denkfehler 😉



  • hep_programmierer schrieb:

    ist eigentlich auch logisch, aber ich dachte dass das die vtable hinkriegt weil doch eigentlich schon bei der übersetzung klar ist, was getan werden soll...

    Wieso sollte das denn klar sein? Bei der Übersetzung der Basisklasse ist ja noch nicht einmal bekannt, welche abgeleiteten Klassen es später geben wird.



  • hmm... ja... die werden ja nicht gleichzeitig kompiliert, wohl wahr...

    das ist wohl auch mein denkfehler 😉

    wieder was dazugelernt, danke!



  • CStoll schrieb:

    hep_programmierer schrieb:

    ist eigentlich auch logisch, aber ich dachte dass das die vtable hinkriegt weil doch eigentlich schon bei der übersetzung klar ist, was getan werden soll...

    Wieso sollte das denn klar sein? Bei der Übersetzung der Basisklasse ist ja noch nicht einmal bekannt, welche abgeleiteten Klassen es später geben wird.

    Das liest sich jetzt so, als ob es da ein technisches Problem geben würde. Tut es aber nicht.

    Vom technischen Standpunkt aus betrachtet wäre es gar kein Problem, es so zu machen, dass immer die Implementierungen der "most derived class" aufgerufen würden, auch schon in den Basisklassen-Konstruktoren.

    Bloss der Standard definiert eben, dass es anders zu passieren hat. Und die Compiler setzen es standardkonform um.

    ----

    hep_programmierer schrieb:

    hmm... ja... die werden ja nicht gleichzeitig kompiliert, wohl wahr...

    Nö, gar kein Denkfehler, siehe oben, wäre technisch ohne weiteres möglich.

    Der Compiler könnte ja intern jederzeit zwei Konstruktor-Funktionen machen: eine für wenn die Klasse "alleinstehend" konstruiert wird, und eine wenn die Klasse als Basisklasse eines anderen Objekts konstruiert wird.
    Die "alleinstehend" Version würde dann z.B. den Vtable Zeiger setzen, und dann die "Teil von Basisklasse" Version aufrufen.
    Und die "Teil von Basisklasse" Version würde den Vtable Zeiger einfach nicht anfassen, da er schon gesetzt ist.
    Wenn es Basisklassen gibt, würde dann der "Teil von Basisklasse" Konstruktor dieser weiteren Basisklassen aufgerufen, der ebenfalls den Vtable Zeiger anfasst etc.
    Der Vtable Zeiger würde also von Beginn an auf die Funktionen der "most derived class" zeigen.

    Dummerweise ergeben sich dadurch ein paar Probleme, wie dass virtuelle Funktionen nie wüssten welche Teile eines Objekts schon initialisiert sind. D.h. man dürfte in solchen virtuellen Funktionen keine Membervariablen angreifen, da sie u.U. noch gar nicht konstruiert sind. Bzw. dürfte man "this" überhaupt nicht verwenden (weder explizit noch implizit). D.h. die ganze Sache wäre ziemlich sinnlos.



  • hep_programmierer schrieb:

    Gibt es da irgendein besonders kluges Rezept für?

    Entweder (wenn möglich) Initialisierungen die abhängig von dem konkreten Typ sind in dem jeweiligen Konstruktor durchführen, oder Factorymethoden zur Objekterzeugung verwenden.



  • hustbaer schrieb:

    CStoll schrieb:

    hep_programmierer schrieb:

    ist eigentlich auch logisch, aber ich dachte dass das die vtable hinkriegt weil doch eigentlich schon bei der übersetzung klar ist, was getan werden soll...

    Wieso sollte das denn klar sein? Bei der Übersetzung der Basisklasse ist ja noch nicht einmal bekannt, welche abgeleiteten Klassen es später geben wird.

    Das liest sich jetzt so, als ob es da ein technisches Problem geben würde. Tut es aber nicht.

    Vom technischen Standpunkt aus betrachtet wäre es gar kein Problem, es so zu machen, dass immer die Implementierungen der "most derived class" aufgerufen würden, auch schon in den Basisklassen-Konstruktoren.

    Bloss der Standard definiert eben, dass es anders zu passieren hat. Und die Compiler setzen es standardkonform um.

    Das technische Problem dahinter hast du weiter unten selber genannt - die Basis-Konstruktoren werden von innen nach außen abgearbeitet, d.h. selbst wenn während der Verarbeitung des Basis-Konstruktors die endgültige VTable bereits existieren würde, wären die Member der abgeleiteten Klasse noch nicht initialisiert. Das schränkt die Möglichkeiten einer (virtuellen) Initialisierungsmethode doch gewaltig ein.
    (afaik arbeitet C# ein wenig anders, aber ich weiß jetzt nicht aus dem Kopf, wie es mit nicht-initialisierten Membern umgeht)



  • @CStoll:
    In C# gibt es keine nicht initialisierten Member, da wird alles hübsch auf Null gesetzt bevor überhaupt irgendein userdefinierter Code anläuft. Was vollkommen ausreichend ist.

    Einzig struct-Member (also Value-Types) wären ein Problem, wenn es für structs erlaubt wäre einen selbst definierten Default-Ctor zu haben (der was anders macht als alle Member null setzten). Ist aber nicht erlaubt, von daher ist es auch kein Problem. Und POD Member sowie Referenzen sind einfach null, bevor mal selbst was reinschreibt.

    Was C++ angeht: um etwas sinnvolles machen zu können, hätte der Standard in weiteren Punkten angepasst werden können. z.B. hätte man definieren können dass das ganze Objekt erstmal ala C# zero-initialized wird. Dadurch wäre dann jederzeit der Zugriff auf POD Member möglich. Was im Prinzip reichen sollte.

    Ich bin ja auch der Meinung dass es gut so ist wie es ist. Aber wie schon gesagt: ich denke technisch wäre es kein echtes Problem.



  • hustbaer schrieb:

    Aber wie schon gesagt: ich denke technisch wäre es kein echtes Problem.

    Rein technisch von der Art "ich kann auf einer üblichen Plattform sowas irgendwie hinbiegen" nicht. Standard-technisch wäre das ein Riesenproblem, es würde z.B. die Konzepte von Objektlebenszeiten völlig über den Haufen schießen. Ausgenullte Member der abgeleiteten Klasse hätten völlig sinnfreie und deshalb inkonsistente Werte (z.B. uninitialisierte Referenzen), und wer virtuelle Methoden der Basisklasse überschreibt, kann nicht mehr davon ausgehen, dass sie auf Membern arbeiten, die so reagieren wie sie _immer_ reagieren (außer dann, wenn sie noch garnicht existieren).

    Sowas zu erlauben würde den Standard nötigen, die Hälfte aller Garantien über Bord zu werfen. *grusel*



  • Ich hab' nie behauptet dass es sinnvoll wäre.

    CStoll hat es so dargestellt als ob es ein Problem für den Compiler wäre. Es ist aber kein Problem für den Compiler, und darauf wollte ich hinweisen.

    pumuckl schrieb:

    (...) Ausgenullte Member der abgeleiteten Klasse hätten völlig sinnfreie und deshalb inkonsistente Werte (...)

    Ja. Mir ist klar dass man den Standard dazu noch an vielen weiteren Stellen anpassen müsste. Es würde einiges ändern. Ich hielte es auch für ziemlich blöd. Trotzdem sehe ich immer noch kein *technisches* Problem.



  • hustbaer schrieb:

    CStoll hat es so dargestellt als ob es ein Problem für den Compiler wäre. Es ist aber kein Problem für den Compiler, und darauf wollte ich hinweisen.

    OK, für den Compiler wäre es vielleicht kein Problem, aber für die virtuelle Methode, die du nutzen willst.
    In C++ ist ein null-initialisiertes Objekt nicht immer sinnvoll zu verwenden, sondern erst nach dem Konstruktor-Aufruf - da reicht mitunter eine kleine Änderung an der Klasse und dir fliegt der gesamte Aufruf um die Ohren. Davon abgesehen wird der Konstruktor der Member auch noch aufgerufen, nachdem die Basisklasse fertig ist - und der geht davon aus, daß er mit dem Objekt alles machen kann ohne auf irgendwelche Vorbelegungen zu achten.


Anmelden zum Antworten