Methode in Initalisierungsliste aufrufen



  • Nein, das ist tatsächlich ein Problem. Stell dir vor, dass die Basisklasse zuerst (und das ist so) konstruiert wird, dann weiss die Klasse ja noch gar nicht, welche vituelle Funktion sie aufrufen soll, respektive das dazugehörige Objekt ist noch nicht erstellt worden.

    Das selbe Problem gibt es bei der Zerstörung, da auch da keine verlässlichen Typinformationen mehr vorhanden sind.



  • Hmmmm - da möchte ich sicher sein, dass ich das auch verstanden habe. Also seht mal das hier:

    class Base {
    public:
       Base() {
          foo();
       }
       virtual void foo() {
          std::cout << "Base" << std::endl;
       }
    };
    
    class Sub {
    public:
       virtual void foo() {
          std::cout << "Sub" << std::endl;
       }
    };
    
    int main() {
       Sub s;
       return 0;
    }
    

    Meine Meiung war (bisher?), dass in Base::Base() die vtbl noch nicht initialisiert ist, so dass das Programm "Base" ausgibt, obwohl ein Sub erstellt wird.

    Schon klar, dass dies wahrscheinlich nicht das ist, was der Programmierer erwartet hat. Ich meinte aber (bisher?), dass der Aufruf legal und definiert ist (es muss Base::foo() aufgerufen werden). Eure Beiträge legen aber nahe, dass ein solcher Aufruf - ja was? - undefiniert ist.

    Seid ihr euch sicher?

    Stefan.



  • Ich gehe mal davon aus, dass Sub von Base erbt.

    So, wie ich das sehe ist es nicht undefiniert (müsste aber auch im Standard genau nachschauen), aber es macht halt keinen Sinn, sondern verwirrt nur unnötig, da es so etwas, wie polymorphie zu diesem Stadium gar nicht geben kann, du aber durch einen virtuellen Aufur implizierst.

    Also mit Vorbehalt:
    Das ist definierter Unsinn. 😉



  • drakon schrieb:

    Ich gehe mal davon aus, dass Sub von Base erbt.

    Oh - ja - natürlich 😉

    drakon schrieb:

    So, wie ich das sehe ist es nicht undefiniert (müsste aber auch im Standard genau nachschauen), aber es macht halt keinen Sinn, sondern verwirrt nur unnötig, da es so etwas, wie polymorphie zu diesem Stadium gar nicht geben kann, du aber durch einen virtuellen Aufur implizierst.

    Also mit Vorbehalt:
    Das ist definierter Unsinn. 😉

    Ja, das sollte man wohl nicht machen. Ich kann mir auch auf die Schnelle keinen Anwendungsfall vorstellen, wo die Nicht-Virtualität im Ctor einer ansonsten virtuellen Methode sinnvoll ist. Aber ich finde es schon wichtig zu wissen, ob ein solcher Aufruf definiert ist.

    Wo wir schon einmal dabei sind. Du schreibst:

    drakon schrieb:

    Das selbe Problem gibt es bei der Zerstörung, da auch da keine verlässlichen Typinformationen mehr vorhanden sind.

    ... und hier habe ich meine Zweifel, ob das stimmt. Ich könnte also nicht in Base eine (protected) Methode cleanup() virtuell definieren und in Base::~Base() aufrufen, die Unterklassen einfach überschreiben, wenn sie auch etwas aufzuräumen haben?

    Meines Wissens ist die vtbl im Dtor noch intakt, so dass ein solches cleanup() tadellos funktionieren müsste.

    Stefan.



  • DStefan schrieb:

    Meines Wissens ist die vtbl im Dtor noch intakt, so dass ein solches cleanup() tadellos funktionieren müsste.

    Probiers doch aus:

    #include <iostream>
    using namespace std;
    
    class Base
    {
        protected:
            virtual void fun()
            {
                cout << "Base" << endl;
            }
        public:
            Base()
            {
                cout << "constructing Base" << endl;
                fun();
                cout << "--------" << endl;
            }
            ~Base()
            {
                cout << "destructing Base" << endl;
                fun();
                cout << "--------" << endl;
            }
    };
    
    class Derived : public Base
    {
        protected:
            void fun()
            {
                cout << "Derived" << endl;
            }
        public:
            Derived()
            {
                cout << "constructing Derived" << endl;
                fun();
                cout << "--------" << endl;
            }
            ~Derived()
            {
                cout << "destructing Derived" << endl;
                fun();
                cout << "--------" << endl;
            }
    };
    
    int main()
    {
        Derived d;
    }
    

    Ausgabe:

    ./main
    constructing Base
    Base
    --------
    constructing Derived
    Derived
    --------
    destructing Derived
    Derived
    --------
    destructing Base
    Base
    --------
    

    Ergo: auch im Destruktor sollte man sich auf virtuelle Funktionen nicht verlassen...

    Wenn du eine virtuelle Funktion aufrufen willst und signalisieren, dass es genau diese eigene Implementierung sein soll, mach das doch so:

    void Base::Base()
    {
        Base::foo();
    }
    


  • Nexus schrieb:

    Sofern die Methoden nicht virtuell sind, ist alles okay. Funktionen gehören schliesslich nicht zur Instanz und sind somit auch nicht von deren Lebenszeit abhängig. Das Problem mit virtuellen Funktionen besteht jedoch darin, dass der this -Zeiger im Konstruktor noch nicht auf die richtige Instanz zeigt. Von daher sollte man von virtuellen Funktionsaufrufen im Konstruktor generell (nicht nur in der Initialisierungsliste) absehen.

    Hmm, sind denn nicht virtuelle nicht statische Methoden nicht auch an den this -Zeiger der jeweiligen Instanz gebunden? Ohne Instanz lassen sie sich schließlich nicht aufrufen.
    Für sowas würde ich eher statische private Funktionen oder Funktionen die in der cpp-Datei verborgen sind benutzen. Sollte die Methode auf Member der Klasse zugreifen müssen, kann einem das genauso gut um die Ohren fliegen, wie bei der Benutzung von virtuellen Funktionen.



  • Tachyon schrieb:

    Hmm, sind denn nicht virtuelle nicht statische Methoden nicht auch an den this -Zeiger der jeweiligen Instanz gebunden? Ohne Instanz lassen sie sich schließlich nicht aufrufen.

    Doch, aufrufen lassen sie sich und es funktioniert auch solange gut, wie man keine Klassenmember benutzt (da sonst ja natürlich der 0 Zeiger dereferenziert wird). Ansonsten ist es ja lediglich ein Funktionsaufruf, mit einem Zeiger, auf die Instanz, welche halt einfach 0 ist, was nicht stört solange man den Zeiger nicht benutzt.

    Habe da letztens was drüber geschrieben: (siehe 14.10.09)
    http://www.drakon.ch/

    Als ich das geschrieben habe, habe ich im Standard eigentlich nichts gefunden, was dafür spricht, dass dieses Vorgehen undefiniertes Verhalten hervorruft. Aber es ist klar, dass es natürlich eigentlich rechter Unfug ist und man eher, wie du sagst statische Funktionen benutzen. (wird auch im Beitrag erwähnt).


  • Mod

    drakon schrieb:

    Als ich das geschrieben habe, habe ich im Standard eigentlich nichts gefunden, was dafür spricht, dass dieses Vorgehen undefiniertes Verhalten hervorruft. Aber es ist klar, dass es natürlich eigentlich rechter Unfug ist und man eher, wie du sagst statische Funktionen benutzen. (wird auch im Beitrag erwähnt).

    undefiniert: 3.8/5 f. für nicht-POD-Klassen (wenn nur der Speicher angefordert wurde, aber kein Klassenobjekt existiert - dann ist es erst recht undefiniert, wenn nicht mal der Speicher angefordert wurde).

    Explizit in 9.3.1/1

    If a nonstatic member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined.

    Das sollte keine Fragen offen lassen. Wiederum können wir den Erst-recht-Schluss wagen: wenn gar kein Objekt existiert (0-Pointer oder ungültiger Zeiger), ist es auch undefiniert.



  • Danke für die Hinweise!
    Ihr erweitert meinen Horizont 😉

    Stefan.



  • @camper:
    Thx für die Info. Werde das bei Gelegenheit bei meinem Beitrag noch ergänzen.


Anmelden zum Antworten