Virtuelle Methoden vom Konstruktor aus aufrufen



  • Hallo Leute!
    Ich bin gerade dabei meinen eigenen kleinen Compiler zu implementieren und stehe vor einer Designentscheidung. Im Grunde geht es darum ob innerhalb eines Konstruktors virtuelle Methoden aus unteren Klassen aufgerufen werden oder aus der selben Klasse. Das folgende C++-Beispiel zeigt das Problem:

    #include <iostream.h>
    
    class Auto {
    public:
      virtual void gibGas(){ cout << "Auto!\n"; }
      Auto(){ gibGas(); }
    };
    
    class Volkswagen : public Auto {
    public:
      void gibGas(){ cout << "Volkswagen!\n"; }
    };
    
    int main( int argc , char **argv ) {
      Volkswagen vw;
      Auto *a = &vw;
      a->gibGas();
      return 0;
    }
    

    Diese Programm(mit g++ compiliert) gibt zuerst Auto! und dann Volkswagen! aus. Ich habe eigentlich erwartet das sofort Volkswagen! ausgegeben wird. Ich frage mich jetzt ob das an g++ liegt oder ist das in C++ so vorgegeben. Die gleichen Beispiele in Java und Delphi geben sofort Volkswagen! aus.

    Was würdet Ihr sagen sollen im Konstruktor virtuelle Methoden aus unteren Klassen aufgerufen werden oder nicht ? Mich würde auch interessieren wie andere C++-Compiler damit umgehen.

    Vielen Dank im voraus!



  • ich weiß ja nicht, aber ist es nicht so, dass zuerst der konstuktor von auto aufgerufen wird? und dieser gibt ruft dann zuerst sein gibGas() auf... dann wird dem pointer die adresse von vw zugewiesen und dann die funktion von volkswagen aufgerufen...

    also finde ich, dass die ausgabe nichts überraschendes ist...



  • Ich habe es jetzt nicht ausprobiert, aber die erste Funktion in der Klasse Auto ist virtuell, die zweite aber nicht. Es müsste eigentlich funktionieren, wenn auch gibGas in Volkswagen virtuell definiert ist.



  • Beim erstellen des Volkswagen-Objekts wird zuerst der Konstruktor von Auto aufgerufen aber die Methode gibGas() ist ja virtuell und theretisch müsste er dann gibGas() aus der Klasse Volkswagen aufrufen. Wie gesagt vergleichbare Beispiele in Java und Delphi geben sofort Volkswagen aus. Im Grunde geht es darum wann die VMT micht welchen Adressen der Methoden aufgefüllt wird und bei g++ ist es wohl so, dass nicht sofort die Adresse von Volkswagen::gibGas() in der VMT steht.



  • Die Methode gibGas() in Volkswagen ist automatisch virtuell weil gibGas() in Auto als virtuell deklariert ist. Volkswagen::gibGas() zusätzlich als virtual deklarieren ändert nichts, habe das ausprobiert.



  • während der Basisklassenkonstruktor ausgeführt wird, ist der dynamische Typ des Objektes auch auf den Basisklassentyp geändert. Das Objekt macht sozusagen während der Konstruktion (und auch während der Destruktion) einen Typwechsel durch.

    Das ist in der Theorie dadurch begründet, dass der Konstruktor die Aufgabe hat, die Invariante der Klasse aufzustellen. Solange das nicht geschehen ist, kannst du keine Methode der Klasse, die sich auf diese Invariante verläßt aufrufen. Eigentlich ist es IMO in Java falsch gelöst, aber anscheinend ist es praktischer 😉


  • Mod

    MasterG schrieb:

    Diese Programm(mit g++ compiliert) gibt zuerst Auto! und dann Volkswagen! aus. Ich habe eigentlich erwartet das sofort Volkswagen! ausgegeben wird. Ich frage mich jetzt ob das an g++ liegt oder ist das in C++ so vorgegeben. Die gleichen Beispiele in Java und Delphi geben sofort Volkswagen! aus.

    Was würdet Ihr sagen sollen im Konstruktor virtuelle Methoden aus unteren Klassen aufgerufen werden oder nicht ? Mich würde auch interessieren wie andere C++-Compiler damit umgehen.

    Vielen Dank im voraus!

    Wenn der konstruktor von Auto ausgeführt wird, existiert der Volkswagen noch gar nicht, sondern nur der auto teil davon. wenn es an dieser stelle erlaubt wäre, virtuelle methoden einer abgeleiteten klasse aufzurufen, würde das unangenehme konsequenzen haben. z.b. könnte die überladene methode ja auf member zugreifen, die in der basisklasse gar nicht existieren. der volkswagen ist erst ein vollwertiger volkswagen, wenn die konstruktion des objekts im funktionsblock des volkswagen konstruktors angekommen ist. der umgekehrte vorgang findet beim zerstören des objektes statt. im übrigen vermisse ich sowieso einen virtuellen destruktor. beispiel:

    #include <iostream>
    using namespace std;
    
    class Auto {
    public:
      virtual void gibGas(){ cout << "Auto!\n"; }
      Auto(){ gibGas(); }
      virtual ~Auto() { cout << "~Auto!\n"; gibGas(); }
    };
    
    class Volkswagen : public Auto {
    public:
      void gibGas(){ cout << "Volkswagen!\n"; }
      Volkswagen() { gibGas(); }
      ~Volkswagen() { cout << "~Volkswagen!\n"; gibGas(); }
    };
    
    int main()
    {
        {  
            Volkswagen vw;
        }
        char x;
        cin >> x;
        return 0;
    }
    

    der aufruf von virtuellen methoden im konstruktor ist erlaubt (könnte auch indirekt passieren, konstruktor ruft normale memberfunktion auf, welche wiederum eine virtuelle methode aufruft), allerdings möglicherweise ineffizient, weil der vtbl pointer u.U. mehrfach modifiziert werden muss (beim direkten call aus dem konstruktor heraus ist der dynamische typ gleich dem statischen typ, beim indirekten aufruf kann der compiler das aber nicht immer feststellen).



  • Bashar schrieb:

    Eigentlich ist es IMO in Java falsch gelöst, aber anscheinend ist es praktischer 😉

    Gibt es denn überhaupt Beispiele, in denen die C++-Variante einen praktischen Vorteil hat? (Nur so nebenbei, vielleicht kann ich mich dann ja irgendwann mit der Designentscheidung anfreunden...)



  • Ich finde es schon ganz gut wie es in C++ implementiert wurde. Es könnte wirklich gefährlich sein wenn aus dem Konstruktor eine abgeleitete Methode aufgerufen wird die evtl. auf Objektvariablen der abgeleiteten Klasse zugreift.
    Hat alles seine Vor- und Nachteile.

    Ich werde es wohl in meinem Compiler so wie bei C++ machen. Dadurch kann der Programmierer nicht soviel "Unsinn" machen.

    Danke für die Beiträge.


Anmelden zum Antworten