Destruktor in abstrakter Basisklasse nötig?



  • Hallo mal wieder.

    Ich hab hier eine abstrakte Basisklasse (quasi ein C++ - Interface 😉 ), um halt ein paar Funktionen für die ableitenden Klassen zu erzwingen.

    Ich poste einfach mal den Code:

    class IBase
    {
        virtual doThis() = 0;
        virtual doThat() = 0;
    };
    
    class A: public IBase
    {
        A();
        virtual ~A();
        virtual doThis();
        virtual doThat();
    };
    
    class B: public IBase
    {
        B();
        virtual ~B();
        virtual doThis();
        virtual doThat();
    };
    

    Nun instanziiere ich nur eine dieser abgeleiteten Klassen, weil jede eine Spezialisierung ist und ich eben nur eine bestimmte haben will. Daher muss ich für die Instanz einen Pointer der Basisklasse nutzen.

    IBase* pInstance;
    switch(condition)
    {
    case 1: pInstance = new A();
            break;
    case 2: pInstance = new B();
            break;
    default:pInstance = NULL;
    }
    

    Nun meine Frage: Eigentlich brauch ich doch immer einen virtuellen Destruktor in der Basisklasse, wenn ich ein davon abgeleitetes Objekt zerstören will mittels

    delete pInstance;
    

    Wieso brauch ich das hier nicht? Zumindest meckern weder Compiler noch Linker rum. Und wenn ich einen virtuellen Destruktor in IBase einfügen will, dann meckert der Linker rum, obwohl der Destruktor in den Klassen A und B implementiert wurde...

    Kann mir das mal bitte einer erklären?



  • Wenn die Basisklasse keinen virtuellen Destruktor hat, wird automatisch ein "normaler" Dtor angelegt - ergo sind Compiler und Linker glücklich, aber du vermutlich nicht (spätestens beim delete pInstance; wirst du sehen, was ich meine ;)). Wenn du der Basisklasse einen virtuellen Dtor spendierst, lebst du generell gesünder - aber du mußt dem Destruktor auch einen Rumpf verpassen (selbst wenn er abstrakt sein sollte):

    class IBase
    {
        virtual doThis() = 0;
        virtual doThat() = 0;
        virtual ~IBase() {};
    };
    ...
    

    (so sind wirklich alle rundum zufrieden :D)

    PS: Ich hoffe, die fehlenden ; hinter den Klassendefinitionen sind ein C&P-Fehler.



  • Ah ja... danke 🙂

    Ich hatte

    virtual ~IBase()=0;
    

    und

    virtual ~IBase();
    

    probiert, aber das ging halt beides nicht (Linkerfehler). Beim zweiten kann ich mir das ja auch noch erklären ohne Rumpf und wegen dem ersten: Ich glaub ich hab mal irgendwo gelesen, dass es keine abstrakten Destruktoren gäbe... dann wundert mich das auch nicht wirklich.

    Aber kann man da jetzt trotzdem noch von einer abstrakten Klasse oder sogar im theoretischen Sinne von einem Interface sprechen? Eigentlich ja oder? Falls ich das mal jemand erklären soll, warum und wieso ich das gemacht hab... 😉

    EDIT: Ich hab mal die ; an die Klassen rangehängt, war ein grober Schusselfehler *schäm*, kein C&P 😉 Hab ja alles nur so aus der Hand hingeschrieben...



  • Eine Klasse ist akstrakt, sobald sie mindestens eine akstrakte Methode hat 😉

    Und ja, es gibt sogar abstakte Destruktoren, aber wie gesagt mußt du denen trotzdem einen Rumpf spendieren:

    class IBase
    {
    public:
      ...
      virtual ~IBase() = 0;
    };
    
    IBase::~IBase() {}
    

    (der Grund ist ganz einfach der, daß die Dtoren der abgeleiteten Klassen auf jeden Fall den Basis-Dtor benötigen, um ausgeführt werden zu können.)

    PS: So ein Konstrukt (akstrakte Methode MIT Methodenrumpf) klappt sogar mit beliebigen Methoden 😉 Diese Methode kannst du dann von der abgeleiteten Klasse aus explizit aufrufen.



  • Ach Mist... noch ein Schusselfehler... war irgendwie gerade der Meinung, dass es sich nur um eine abstrakte Klasse handelt, wenn ALLE Methoden abstrakt sind... 😉

    (der Grund ist ganz einfach der, daß die Dtoren der abgeleiteten Klassen auf jeden Fall den Basis-Dtor benötigen, um ausgeführt werden zu können.)

    Das war mir ja klar, daher ja auch dieser Thread, denn ich konnte mir nicht so recht vorstellen, dass das der Standarddestruktor der Basisklasse übernimmt und ich wollte ja gerne Speicherlöcher vermeiden.

    Nun geht's aber auf alle Fälle. Nur kann ich nicht wirklich von einem Interface reden, da ich ja bereits eine Instanz vom Typen IBase halte, was ja bei reinen Interfaces nicht passieren dürfte oder? Aber bitte bitte jetzt keine Diskussion anfangen, dass es keine Interfaces bei C++ gibt *liebguck*, der "Entwurf Interface" geht ja eigentlich doch zu implementieren...

    PS:

    IBase::~IBase[] {}
    

    Da haste aber nun auch geschusselt, du meinst doch bestimmt runde anstatt eckiger Klammern? 😃 Wird wohl für alle Zeit für's Wochenende 🙂 Oder kenn ich da mal wieder irgendwas so gar nicht? 😮



  • Knaecke schrieb:

    (der Grund ist ganz einfach der, daß die Dtoren der abgeleiteten Klassen auf jeden Fall den Basis-Dtor benötigen, um ausgeführt werden zu können.)

    Das war mir ja klar, daher ja auch dieser Thread, denn ich konnte mir nicht so recht vorstellen, dass das der Standarddestruktor der Basisklasse übernimmt und ich wollte ja gerne Speicherlöcher vermeiden.

    Nun geht's aber auf alle Fälle. Nur kann ich nicht wirklich von einem Interface reden, da ich ja bereits eine Instanz vom Typen IBase halte, was ja bei reinen Interfaces nicht passieren dürfte oder? Aber bitte bitte jetzt keine Diskussion anfangen, dass es keine Interfaces bei C++ gibt *liebguck*, der "Entwurf Interface" geht ja eigentlich doch zu implementieren...

    Nein, du hast keine Instanz von IBase - du hast einen "Zeiger auf IBase" und der zeigt zur Laufzeit auf eine Instanz von A oder B (oder eine andere Klasse, die "das Interface IBase implementiert" hat).

    PS:

    IBase::~IBase[] {}
    

    Da haste aber nun auch geschusselt, du meinst doch bestimmt runde anstatt eckiger Klammern? 😃 Wird wohl für alle Zeit für's Wochenende :)Oder kenn ich da mal wieder irgendwas so gar nicht? 😮

    Ne, das war Absicht. Ich wollte sehen, ob du noch bei der Sache bist 😃



  • CStoll schrieb:

    ...
    PS: So ein Konstrukt (akstrakte Methode MIT Methodenrumpf) klappt sogar mit beliebigen Methoden ...

    Was aber an der "Abstraktheit" der Klasse nichts ändert - sie ist weiterhin nicht direkt instantiierbar. (War mal ein Mißverständnis von mir)

    Eine Frage habe ich doch noch (Neugier): Als "Basisklassenteil einer abgeleiteten Klasse" ist die abstrakte Klasse ja (implizit) trotzdem instantiiert, oder ? Wird während der Konstruktion (vor dem Aufruf des BasisklasseCtors) eigentlich in die vtable etwas eingetragen, oder bedeutet das "=0" eben, dass hier NICHTs eingetragen wird ?
    Ich hatte das so verstanden, dass beim Ableiten vor Ausführen des "BasisklassenCtor-Rumpfes" die vtable angelegt und die (virtuellen) Basisklassenfunktionen dort eingetragen (und vor Ausführen des "KindklassenCtor-Rumpfes" von deren v-Funktionen überschrieben) werden.
    Ist das richtig ? Oder total implementationsabhängig ?

    Gruß,

    Simon2.



  • Ich hätte auch gedacht, dass trotzdem irgendwie eine Instanz der Basisklasse angelegt wird, denn wenn ich schon einen Methodenrumpf hab, dann muss der doch auch irgendwie für eine Abarbeitung existieren?



  • Knaecke schrieb:

    Ich hätte auch gedacht, dass trotzdem irgendwie eine Instanz der Basisklasse angelegt wird, denn wenn ich schon einen Methodenrumpf hab, dann muss der doch auch irgendwie für eine Abarbeitung existieren?

    Methoden existieren einmal pro Klasse und können von allen Instanzen genutzt werden. Eine Instanz wird erst erzeugt, wenn du sie selber anlegst.

    @Simon: Afaik ist es komplett dem Compiler überlassen, wie er virtuelle Funktionen implementiert (aber eine vtable) ist wohl der gängigste Weg. Von daher ist erst recht nicht festgelegt, wie er abstrakte Methoden (mit oder ohne Methodenrumpf) behandelt. Und die Bedingung "alle abstrakten Methoden müssen überschrieben werden" dürften normale Compiler bereits auf syntaktischer Ebene kontrollieren, ohne die vtable zu bemühen.

    (auch wenn das "=0;" sehr an eine Zuweisung erinnert, ist nicht einmal geagt, daß in der vtable wirklich NULL-Einträge landen)



  • CStoll schrieb:

    Knaecke schrieb:

    Ich hätte auch gedacht, dass trotzdem irgendwie eine Instanz der Basisklasse angelegt wird, denn wenn ich schon einen Methodenrumpf hab, dann muss der doch auch irgendwie für eine Abarbeitung existieren?

    Methoden existieren einmal pro Klasse und können von allen Instanzen genutzt werden. Eine Instanz wird erst erzeugt, wenn du sie selber anlegst.

    Hm stimmt... Ist ja nix anderes, als wenn ich von einer normalen Klasse erbe, dann kann ich deren Methoden ja genauso verwenden... ich sollte echt mehr überlegen bevor ich was schreibe heute... 🙄

    Na gut, aber trotzdem wieder was dazugelernt mit dem Destruktor 🙂 Dankeschön 👍



  • CStoll schrieb:

    ...
    @Simon: ...

    Aha !!

    Danke,

    Simon2.

    P.S.: Ich hatte noch nie "=0" mit einer Zuweisung verwechselt ... aber ich denke, es soll trotzdem an eine erinnern.


Anmelden zum Antworten