Wann ist ein default constructor erforderlich



  • Hi,

    Ich habe folgende Vererbungshierarchie:

    class Base
    {
    public:
       Base();
    
       Func(...);
    
    private:
       // Mebmervariablen
    
    };
    
    class Derived1 : public Base
    {
    public:
       Func(...);
    };
    
    class Derived2: public Derived1
    {
    public:
       Func(...);
    };
    

    Die Klassen Derived1 und Derived2 überladen also nur eine Methode; es kommen keine weiteren Membervariablen hinzu.
    Meine Frage ist nun: brauchen die beiden abgeleiteten Klassen einen default consructor oder reicht der implizit generierte? Nach meinem Verständnis wird beim Aufruf des Konstruktors der abgeleiteten Klasse zunächst der Konstruktor der Oberklasse aufgerufen, welcher wiederum zuerst seinen Oberklassenkonstruktor aufruft, etc.
    Ich würde hier also davon ausgehen, dass ich für keine der beiden abgeleiteten Klassen einen (leeren) default constructor implementieren muss. Ist das richtig?



  • Hi,

    mal eine Gegenfrage: Was würdest Du ein Deinem default-Ctor der abgeleiteten Klassen machen wollen? 😉
    ... mal im Ernst: Es reicht der automatisch generierte - der kümmert sich schon um den "hierarchischen Aufruf".
    Wichtig sind vielleicht noch 2 Dinge:
    - "Verdecken" != "Überschreiben": Wenn das Verhalten von Func "polymorph" sein solle (also für jede abgeleitete Klasse spezifisch), solltest Du es als "virtual" deklarieren. Sonst hast Du "Überdeckung".
    - "Überladen" != "Überschreiben": Wenn Du die Signatur von Func (merke: Returntyp gehört nicht zur Signatur) in den abgeleiteten Klassen änderst, hast Du wieder keine Polymorphie, sondern fügst eine Überladung dazu - hast also ZWEIMAL Func in Deiner abgeleiteten Klasse.

    Gruß,

    Simon2.



  • Danke für die schnelle Antwort und auch für den kurzen Exkurs bezüglich "Überschreiben" und "Überladen".
    Hier war schon "Überladen" gemeint; die überladene Methode hat in Wirklichkeit dieselbe Signatur wie in den Oberklassen und ist natürlich auch virtual.

    Was die eigentliche Frage anging, habe ich mich ein wenig durch einen Linker-Fehler aus dem Konzept bringen lassen. Ich hatte einen "leeren" default ctor in einer der abgeleiteten Klassen stehen, dachte dann aber, er sei aus den genannten Gründen überflüssig und habe ihn gelöscht. In einer anderen Datei hatte ich ein Objekt der abgleiteten Klasse erstellt, diese Datei aber nicht neu übersetzt und im obj-File wurde dann vermutlich der gelöschte default ctor vermisst.

    Hat sich also inzwischen von selbst erledigt und war eigentlich ein ziemlich überflüssiger Post... Sorry!

    Aber trotzdem danke für die Hilfe!



  • Der Nikolaus schrieb:

    Hier war schon "Überladen" gemeint; die überladene Methode hat in Wirklichkeit dieselbe Signatur wie in den Oberklassen und ist natürlich auch virtual.

    Also doch nicht Überladen. 😉

    Von Funktionsüberladung ist die Rede, wenn der gleiche Funktionsname für mehrere Signaturen benötigt wird. Bei Polymorphie sind andere Funktionen in der Klasse, die zwar den gleichem Namen tragen, aber in einer Basisklasse deklariert wurden, nicht mehr sichtbar. Man spricht dabei von Verdeckung.



  • Nexus schrieb:

    Also doch nicht Überladen. 😉

    Nachdem ich mir das auf Hume Sikkins' Homepage noch einmal durchgelesen habe (hier ist mal wieder ein Lob fällig!), stimme ich dir zu: nein, überladen ist tatsächlich Blödsinn 🤡

    Nexus schrieb:

    Bei Polymorphie sind andere Funktionen in der Klasse, die zwar den gleichem Namen tragen, aber in einer Basisklasse deklariert wurden, nicht mehr sichtbar. Man spricht dabei von Verdeckung.

    Hume Sikkins schrieb:

    Überschreiben schließlich bezieht sich auf signaturgleiche virtuelle Memberfunktionen in Klassenhierarchien. Eine virtuelle Memberfunktion einer Basisklasse wird von einer signaturgleichen Memberfunktion einer abgeleiteten Klasse überschrieben. Beim Aufruf einer überschriebenen Memberfunktion bestimmt der dynamische Typ des aufrufenden Objekts welche Version aufgerufen wird

    Nach der Lektüre würde ich aber das, was ich hier gemacht habe auch nicht als Verdeckung sondern als Überschreiben bezeichnen: 😕

    Der Nikolaus schrieb:

    die [...] Methode hat in Wirklichkeit dieselbe Signatur wie in den Oberklassen und ist natürlich auch virtual.

    Naja, wie dem auch sei... Wichtig ist ja vor allem, dass man das unterschiedliche Verhalten in den verschiedenen Fällen kennt. Ich hoffe, jetzt weiß ich auch, wie man sie korrekt bezeichnet.



  • Der Nikolaus schrieb:

    ...
    Nach der Lektüre würde ich aber das, was ich hier gemacht habe auch nicht als Verdeckung sondern als Überschreiben bezeichnen: 😕 ...

    Ich auch - jedenfalls, sobald Base::Func() deklariert ist als

    class Base {
    ...
       virtual .... Func();
    

    ohne "virtual" kommt es eben leider zur Überdeckung, die Du vermutlich nicht willst.
    Übrigens (vielleicht nur ein Flüchtigkeitsfehler) sollte Func einen Returntyp haben - da wo in meinem Beispiel die 4 (!) Punkte stehen.

    Ich finde es im Deutschen immer ein wenig schwieriger mit der exakten Wortwahl; im Englischen ist es einfacher (für mich):

    void f(); 
    void f(int); // overloading von f()
    
    struct B {
       virtual void f();
       void g();
       virtual void h();
    };
    
    struct D : B {
       virtual void f(); // overriding A::f()
       void g(); // hiding A::g()
       virtual void h(int); // (vermutlich versehentliches) overloading von A::h()
    };
    
    int main() {
       f();
       f(3);
       B b;
       D d;
       B& b_ref = d;
    
       b.f();
       d.f();
       b_ref.f();
    
       b.g();
       d.g();
       b_ref.g();
    
       b.h();
       d.h();
       b_ref.h();
    
       b.h(5);
       d.h(5);
       b_ref.h(5);
    
       return 0;
    }
    

    Die Funktionsrümpfe seien dem Lernwilligen zur Hausaufgabe überlassen. 😉

    Gruß,

    Simon2.



  • Simon2 schrieb:

    ohne "virtual" kommt es eben leider zur Überdeckung, die Du vermutlich nicht willst.

    Wobei das nur gilt, sofern in der Basisklasse auch kein virtual steht - falls es dort bereits vorhanden ist, braucht man es in abgeleiteten Klassen nicht mehr zu schreiben (es empfiehlt sich jedoch).

    Simon2 schrieb:

    virtual void h(int); // (vermutlich versehentliches) overloading von A::h()
    

    Auch hier möchte ich noch etwas ergänzen: Es handelt sich nicht um normale Überladung, da die Funktion virtual void h(void) nach der Deklaration von virtual void h(int) nicht mehr sichtbar ist. Wenn man also die Basisklassenversion ohne Parameter aufrufen möchte, muss man das explizit über die Klassenangabe tun (das war auch der Grund, wieso ich von Verdecken und nicht Überladen sprach).



  • Danke für die Ergänzung, Nexus,

    Simon2 schrieb:

    virtual void h(int); // (vermutlich versehentliches) overloading von A::h()
    

    Auch hier möchte ich noch etwas ergänzen: Es handelt sich nicht um normale Überladung, ...[/quote]
    Das stimmt natürlich, aber es ist eben die typische Fehlersituation bei Leuten, die mit overload und override nicht ganz sicher sind.

    Das hiding kommt dann noch dazu - aber (zumindestens nach meinem Verständnis) eben dadurch, dass eine "namens- aber nicht signaturgleiche" Funktion hinzukommt (overloading). Aber das sind vermutlich eher akademische Philosophiediskussionen - im Kern sind wir uns einig....

    Gruß,

    Simon2.


Anmelden zum Antworten