Abstrakte Destruktoren



  • das = 0 beim destruktor hat keine auswirkung auf das arbeiten mit der klasse; man kann eine eigene definition bei abgeleiteten klassen ebenso weglassen, als würde = 0 nicht verwendet. es reicht aus virtual zu benutzen:

    #include <iostream>
    
    using std::cout;
    using std::endl;
    
    class foo1
    {
    public:
    	virtual ~foo1() { cout << "foo1 destruktor" << endl; }
    };
    
    class foo2
    {
    public:
    	~foo2() { cout << "foo2 destruktor" << endl; }
    };
    
    class bar1 : public foo1
    {
    public:
    	~bar1() { cout << "bar1 destruktor" << endl; }
    };
    
    class bar2 : public foo2
    {
    public:
    	~bar2() { cout << "bar2 destruktor" << endl; }
    };
    
    int main()
    {
    	foo1 *f1 = new bar1();
    	foo2 *f2 = new bar2();
    
        // da der destruktor von foo1 virtuell ist,
        // wird erst der destruktor von bar1
        // und dann der destruktor von foo1 aufgerufen
    	delete f1;
    
    	// der destruktor von foo2 ist nicht virtuell,
        // also wird der destruktor von bar2 nicht aufgerufen,
        // somdern nur der von foo2
    	delete f2;
    
    	std::cin.get();
    }
    

    hier noch eine kleine demonstration der sinnlosigkeit von = 0 beim destruktor:

    #include <iostream>
    
    class sinnlos
    {
    public:
        virtual ~sinnlos() = 0;
    };
    sinnlos::~sinnlos() {}
    
    class pustekuchen : public sinnlos
    {
    public:
        pustekuchen() { std::cout << "pustekuchen" << std::endl; }
        //um den destruktor braucht man sich, trotz = 0, nicht selbst zu kümmern
    };
    
    int main()
    {
        pustekuchen p;
        std::cin.get();
    }
    


  • Simon2 schrieb:

    Hmmm, ich hätte auch immer gedacht, dass das eigentlich nicht geht; aber vermutlich habe ich die Bedeutung von "= 0" mißverstanden.
    Ich dachte immer, das hieße: "Gibt's keine Definition von."

    Ich empfehle bezüglich dieser Problematik "More exceptional C++" von Herb Sutter
    "Item27: (Im)pure virtual Functions"



  • Braunstein schrieb:

    ...
    Ich empfehle bezüglich dieser Problematik "More exceptional C++" von Herb Sutter
    "Item27: (Im)pure virtual Functions"

    Das ist gerade das einzige aus der Reihe, das ich noch nicht habe ... 😉

    Gruß,

    Simon2.



  • zerstörer schrieb:

    das = 0 beim destruktor hat keine auswirkung auf das arbeiten mit der klasse; man kann eine eigene definition bei abgeleiteten klassen ebenso weglassen, als würde = 0 nicht verwendet. es reicht aus virtual zu benutzen

    ich erlaube mir anzumerken, daß der abstakte destruktor die klasse abstrakt macht. versuch doch mal ein sinnlos-objekt anzulegen.



  • Mein Absicht war ja nur in C++ zu sagen:

    1.) In der abstrakten Basisklasse gibts keinen Destruktor (Was soll der tun?)
    2.) Wenn jemand einen Destruktor braucht, dann muß er virtuell sein.

    Ich habe nochmal das dicke Buch vom Herrn Stroustrup gewälzt. Da ist ein Beispiel für eine abstrakte Klasse. Die hat im Header einen leeren virtuellen Destruktor:

    virtual ~lval_box() {}
    

    Das scheint also durchaus die korrekte Vorgehensweise zu sein.



  • volkard schrieb:

    ich erlaube mir anzumerken, daß der abstakte destruktor die klasse abstrakt macht. versuch doch mal ein sinnlos-objekt anzulegen.

    das ist natürlich richtig; ich habe mich falsch ausgedrückt. für stimpleton macht es halt keinen unterschied, da er ja sowieso abstrakte methoden in der basisklasse hat.



  • Der Großteil des Buches ist auch im Netz zu finden...

    http://www.gotw.ca/gotw/031.htm

    Das muß ich jetzt selber erstmal lesen...



  • zerstörer schrieb:

    das ist natürlich richtig; ich habe mich falsch ausgedrückt. für stimpleton macht es halt keinen unterschied, da er ja sowieso abstrakte methoden in der basisklasse hat.

    ok. aber auch daran zweifle ich. kann mir irgendwie nicht vorstellen, daß man einen pur virtuellen destruktor in der unterklasse nicht redefinieren muss.



  • volkard schrieb:

    kann mir irgendwie nicht vorstellen, daß man einen pur virtuellen destruktor in der unterklasse nicht redefinieren muss.

    der default-destruktor reicht offensichtlich aus. der basisklassen-destruktor ist ja in wirklichkeit definiert, so dass abgeleitete klassen ihn aufrufen können, und das "pur virtuell" ist in dem fall nur eine fassade um zu erzwingen, dass die basisklasse abstrakt ist. sehe ich persönlich auch als eine sehr merkwürdige design-technik an, weil man als client (ableiter) nicht sofort weiß, was denn nun konkretisiert werden soll. Da kann man ja konkretisieren, indem man einfach schreibt:

    class konkret : public abstrakt {};
    

    das kann doch nicht der sinn von abstrakten klassen sein, oder?



  • zerstörer schrieb:

    Da kann man ja konkretisieren, indem man einfach schreibt:

    class konkret : public abstrakt {};
    

    das kann doch nicht der sinn von abstrakten klassen sein, oder?

    unwahrscheinlich.
    so, wie man auch nicht schreibt

    int main(){42;}
    

    aber korrekt kannst doch sein.



  • volkard schrieb:

    ...kann mir irgendwie nicht vorstellen, daß man einen pur virtuellen destruktor in der unterklasse nicht redefinieren muss.

    (nur aus Test: )
    Aber das Ergbnis ist dasselbe, ob nun mit "=0" oder ohne: Compiler hat kein Problem und Linker meckert fehlende Funktion an.

    So gesehen wäre das "=0" tatsächlich überflüssig.

    Gruß,

    Simon2.



  • So gesehen wäre das "=0" tatsächlich überflüssig.

    ... da die Abstraktheit der Klasse durch andere abstrakte Funktionen garantiert ist.

    Falls sonst keiner virtuell sein mag, dann nehmen wir den Destruktor und machen ihn abstrakt.

    Herb Sutter: If the class should be abstract but it doesn't happen to have any other pure virtual functions, a common technique to make the destructor pure virtual:

    Allgemein läßt sich somit sagen: C++ ist nicht ganz einfach und kann zu erheblicher Verwirrung führen. 🤡



  • volkard schrieb:

    zerstörer schrieb:

    das ist natürlich richtig; ich habe mich falsch ausgedrückt. für stimpleton macht es halt keinen unterschied, da er ja sowieso abstrakte methoden in der basisklasse hat.

    ok. aber auch daran zweifle ich. kann mir irgendwie nicht vorstellen, daß man einen pur virtuellen destruktor in der unterklasse nicht redefinieren muss.

    Das erledigt zur Not der Compiler für dich.



  • finix schrieb:

    volkard schrieb:

    ok. aber auch daran zweifle ich. kann mir irgendwie nicht vorstellen, daß man einen pur virtuellen destruktor in der unterklasse nicht redefinieren muss.

    Das erledigt zur Not der Compiler für dich.

    *mir an die stirn hau*



  • stimpleton schrieb:

    So gesehen wäre das "=0" tatsächlich überflüssig.

    ... da die Abstraktheit der Klasse durch andere abstrakte Funktionen garantiert ist.

    Falls sonst keiner virtuell sein mag, dann nehmen wir den Destruktor und machen ihn abstrakt.....

    Hatte ich auch gelesen (und verstanden) ... allerdings bin ich immer noch ein wneig enttäuscht/überrascht, dass der Standard offensichtlich doch kein "overriding erzwingt", sondern man mit einem einfachen Zweizeiler im eigenen Code sofort die "Abstraktheit übersteuern" kann

    class Concrete : public Abstract {}; 
    Abstract::~Abstract() {}
    

    Hat man einmal für die Basisklasse (!!) eine Dummy-Implementierung hinzugefügt, kann man beliebig oft ableiten (horizontal wie vertikal) und niemandem fällt mehr auf, dass man die Schnittstelle nicht erfüllt hat.

    Ich hätte z.B. erwartet, dass ein Compiler das Vorhandensein einer Implementierung ablehnt, wenn in der Klassendeklaration die Methode mit "=0" gekennzeichnet ist.

    Aber nun gut; muß man eben noch mehr aufpassen.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    allerdings bin ich immer noch ein wneig enttäuscht/überrascht, dass der Standard offensichtlich doch kein "overriding erzwingt",

    Genau das erzwingt er. (Google: default destructor)

    Simon2 schrieb:

    sondern man mit einem einfachen Zweizeiler im eigenen Code sofort die "Abstraktheit übersteuern" kann

    class Concrete : public Abstract {}; 
    Abstract::~Abstract() {}
    

    Dein Code ist mehr oder weniger falsch.

    Simon2 schrieb:

    Hat man einmal für die Basisklasse (!!) eine Dummy-Implementierung hinzugefügt, kann man beliebig oft ableiten (horizontal wie vertikal) und niemandem fällt mehr auf, dass man die Schnittstelle nicht erfüllt hat.

    Deine Vermutung ist falsch, aber selbst wenn - was sollte der Compiler tun?



  • finix schrieb:

    ...

    class Concrete : public Abstract {}; 
    Abstract::~Abstract() {}
    

    Dein Code ist mehr oder weniger falsch.
    ...[/quote]

    Kannst Du das etwas genauer spezifizieren ?
    In diesem Thread habe ich gerade gelernt, dass das Implementieren einer "pure virtual function" durchaus erlaubt ist.

    Was der Compiler tun kann ?
    Nun, so, wie er überprüft, ob die Signatur einer Funktion stimmt, könnte er ein "=0" abchecken....

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Kannst Du das etwas genauer spezifizieren ?
    In diesem Thread habe ich gerade gelernt, dass das Implementieren einer "pure virtual function" durchaus erlaubt ist.

    Jede Klasse muss einen Destruktor haben.
    (->Wenn ~Abstract() nicht implementiert ist (vorzugsweise in Abstract.[c|h]pp) beschwert sich der Linker weil er's nicht findet, wenn du's nochmal implementierst beschwert er sich weil's doppelt ist)

    Simon2 schrieb:

    Was der Compiler tun kann ?
    Nun, so, wie er überprüft, ob die Signatur einer Funktion stimmt, könnte er ein "=0" abchecken....

    Wie sieht's bei folgendem Code aus, was soll der Compiler machen?

    struct foo
    {
      virtual void blob() = 0;
    };
    struct dummy_implemented : foo
    {
      virtual void blob() { }
    };
    


  • finix schrieb:

    ...
    Wie sieht's bei folgendem Code aus, was soll der Compiler machen?

    struct foo
    {
      virtual void blob() = 0;
    };
    struct dummy_implemented : foo
    {
      virtual void blob() { }
    };
    

    Nun, was ein Compiler eben tut:

    • feststellen, dass foo::blob() pure virtual ist und korrekterweise keine Implementation vorliegt.
    • dummy_implemented::blob() entsprechend in die vtable von dummy_implemented aufnehmen

    Dein Beispiel widerspricht ja gar nicht meinem Wunsch, dass der Compiler eine Implementation einer pure virtual function abweisen sollte....

    Wenn ich das recht verstehe, ist der relevante Punkt, dass es eine Destruktor-Implementation geben muß, was natürlich bei anderen Funktionen anders ist (die können entweder ganz fehlen (inkl. Deklaration) oder "pure virtual" sein). Deswegen KANN er die Implementation einer "pure-vit-Dtors" gar nicht abweisen (weil der Linker sie letztlich braucht) ... bei anderen Funktionen könnte er es aber tun.....
    Letztlich hat man nur die Wahl zwischen 2 Inkonsistenzen:

    • Entweder lässt man eine Implementation zu "reinen Funktionsinterfaces" zu
    • oder man behandelt Dtoren anders als andere Funktionen

    Anscheinend hat man sich für Ersteres entschieden - ich persönlich hätte Zweiteres (z.B. in der Variante, dass Dtoren nicht pure virtual sein dürfen) etwas plausibler gefunden (welchen Sinn machen eigentlich abstrakte Basisklassen, die keine "echte" pure-virt-function haben ?).

    Und letztlich bstimmt damit der Satz "eine abstrakte Klasse kann nicht instantiiert werden" nicht in seiner Absolutheit .... weil sie als Basisklasse einer abgeleiteten Klasse doch instantiiert wird - nur eben "automatisch" und niemals "manuell".

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Wenn ich das recht verstehe, ist der relevante Punkt, dass es eine Destruktor-Implementation geben muß

    Nein, nicht ganz: der springende Punkt an diesem Beispiel ist dass der Compiler automatisch einen Destructor einbaut falls du selber keinen deklarierst.
    Dein Beispiel ein wenig abgeändert funktioniert daher nicht wie du befürchtest:

    struct foo
    {
      virtual void blob() = 0;
    };
    
    // ...
    
    struct bar : foo { };
    void foo::blob() { }
    
    // ...
    
    bar brawl; // error: bar is abstract
    

Anmelden zum Antworten