Abstrakte Destruktoren



  • 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
    


  • finix schrieb:

    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....

    Also eben doch: Er baut einen, weil es einen geben muß 😉

    Ich wollte gar nicht ausdrücken, dass der Programmierer einen schreiben muß, sondern eben nur "es muß einen geben" .... und da unterscheidet sich eben der Dtor von blob() und Co. Der Compiler würde nämlich kein blob() selbst bauen.

    Zum Code: Ich meinte, dass ich da ausprobiert habe, habe hier aber gerade keinen Compiler greifbar ....(auf dem Rechner meiner Frau ist eben keiner 😃 ).

    Aber Dein Code stellt genau meine o.g. Alternativen dar:

    • Entweder compiliert er problemlos => man kann also doch "pure-virt-functions" implementieren
    • oder er weist es ab => der Compiler behandelt Dtoren (deren Implementation er ja offensichtlich nicht abweist) anders als andere virt-funcs.

    Wie man es dreht: Ganz "schön" bekommt man es eben nicht hin.
    (wobei ich es schöner fände, wenn er bereits das Auftreten von "foo::blob() {}" anmeckern würde......

    Gruß,

    Simon2.



  • Marc++us schrieb:

    in seinem tollen Buch:
    Wann immer eine Methode, ein Member, eine Methode eines Members oder eine Basisklasse etc.. abstrakt ist, sollte der Destruktor auch abstrakt sein.

    Oder so ähnlich, habs jetzt nicht dabei.



  • Simon2 schrieb:

    Also eben doch: Er baut einen, weil es einen geben muß 😉

    Nein, du verstehst mich nicht richtig. Versuch mein voriges Beispiel zu kompilieren und du wirst, denke ich, sehen was ich meine.

    Der Compiler baut den Destruktor in Concrete - den Destruktor in Abstract musst du implementieren, da du ihn deklariert hast.



  • 1310-Logik schrieb:

    Marc++us schrieb:

    in seinem tollen Buch:
    Wann immer eine Methode, ein Member, eine Methode eines Members oder eine Basisklasse etc.. abstrakt ist, sollte der Destruktor auch abstrakt sein.

    Oder so ähnlich, habs jetzt nicht dabei.

    Ich kenne das Buch nicht, könnte aber wetten dass Marc++us, sinnvollerweise, "virtuell" anstatt "abstrakt" geschrieben hat.



  • finix schrieb:

    1310-Logik schrieb:

    Marc++us schrieb:

    in seinem tollen Buch:
    Wann immer eine Methode, ein Member, eine Methode eines Members oder eine Basisklasse etc.. abstrakt ist, sollte der Destruktor auch abstrakt sein.

    Oder so ähnlich, habs jetzt nicht dabei.

    Ich kenne das Buch nicht, könnte aber wetten dass Marc++us, sinnvollerweise, "virtuell" anstatt "abstrakt" geschrieben hat.

    Kann sein, ich kann mir Merksätze nie merken 🤡



  • finix schrieb:

    Simon2 schrieb:

    Also eben doch: Er baut einen, weil es einen geben muß 😉

    Nein, du verstehst mich nicht richtig. Versuch mein voriges Beispiel zu kompilieren und du wirst, denke ich, sehen was ich meine.

    Der Compiler baut den Destruktor in Concrete - den Destruktor in Abstract musst du implementieren, da du ihn deklariert hast.

    Hi,

    also ich habe jetzt mal ein wenig getestet:

    struct foo
    {
      virtual ~foo() = 0;
      virtual void f() = 0;
    };
    
    struct bar : foo { };
    foo::~foo() { }
    void foo::f() {}
    

    Ergebnis:

    • Ohne Instantiierung eines bar hat der Compiler keine Probleme
    • Mit Instantiierung stößt er sich an "der Virtualität von f" an der Stelle der Implementierung von foo::f().
    • Mit Einführung von bar::f() ist alles OK.
    • Ohne Implementation von ~foo() hat der Compiler keine Probleme, aber der Linker moniert den fehlenden Dtor.

    Ergo => Der Compiler handhabt pure-virt-Dtors anders als "normale pure-virt-funcs" .... so, wie ich vermutet habe. Bei dem Einen duldet (ja sogar: fordert implizit) er etwas, was er beim Anderen verbietet.

    Aber Du hast Recht mit: Dass der Compiler selbst einen foo-Dtor baut, stimmt nicht !
    War zwar auch nicht so gemeint, aber in dem Zusammenhang wirklich mißverständlich von mir.
    Einig sind wir uns aber wohl mit: Der Compiler erzeugt auf jeden Fall einen Aufruf des foo-Dtors automatisch, wenn man ein bar-Objekt instantiiert.

    Gruß,

    Simon2.



  • Vielleicht wäre es hilfreich wenn du versuchst dieses Experiment noch einmal unvoreingenommen zu interpretieren.



  • finix schrieb:

    Vielleicht wäre es hilfreich wenn du versuchst dieses Experiment noch einmal unvoreingenommen zu interpretieren.

    😕

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Ergo => Der Compiler handhabt pure-virt-Dtors anders als "normale pure-virt-funcs" .... so, wie ich vermutet habe. Bei dem Einen duldet (ja sogar: fordert implizit) er etwas, was er beim Anderen verbietet.

    Nein, nicht wirklich. Der einzige Unterschied ist dass jede Klasse (logischerweise) einen Destructor benötigt und der Compiler (praktischerweise) ggf. implizit einen Destructor generiert, ob (pure) virtual oder nicht.

    Unabhängig davon ist der Gedanke den Destructor überschreiben zu müssen ohnehin recht unsinnig, denn das ist, ähnlich wie beim Constructor, schlicht nicht möglich.



  • Hi,

    ich habe das Gefühl, dass wir uns immer weniger verstehen, je länger wir uns unterhalten.

    Der von mir genannte Unterschied im Handling der Implementation von "pure-virt-DTors" und "pure-virt-funcs" ist (auch auf die Gefahr, mich zu wiederholen):
    - die einen werden benötigt (Dtors),
    - die anderen werden verboten (Funcs)

    finix schrieb:

    ...der Compiler (praktischerweise) ggf. implizit einen Destructor generiert, ob (pure) virtual oder nicht...

    Also MEIN Compiler (gcc 3.4.4) generiert bei der Angabe von "= 0" KEINEN Dtor, sondern läuft auf einen entsprechenden Linkerfehler.

    finix schrieb:

    ...
    Unabhängig davon ist der Gedanke den Destructor überschreiben zu müssen ohnehin recht unsinnig, denn das ist, ähnlich wie beim Constructor, schlicht nicht möglich.

    Was meinst Du mit "Überschreiben" ?
    Normale Polymorphie mit Destruktoren ist nicht nur erlaubt, sondern geboten .... mit Konstruktoren dagegen wirklich nicht möglich (bzw. nur über Spezialtechniken).
    Wahrscheinlich meinst Du etwas Anderes mit dieser Aussage.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Also MEIN Compiler (gcc 3.4.4) generiert bei der Angabe von "= 0" KEINEN Dtor, sondern läuft auf einen entsprechenden Linkerfehler.

    Zum Mitschreiben: 😉
    1. jede Klasse hat einen dtor
    2. deklarierst Du ihn nicht explizit, generiert der Compiler einen
    3. deklarierst Du ihn explizit, musst Du ihn auch definieren, sonst ➡ Linkerfehler
    4. deklarierst Du einen pure virtual dtor, ist 2. nicht erfüllt. Daraus folgt 3.



  • LordJaxom schrieb:

    ...
    Zum Mitschreiben: 😉
    1. jede Klasse hat einen dtor
    2. deklarierst Du ihn nicht explizit, generiert der Compiler einen
    3. deklarierst Du ihn explizit, musst Du ihn auch definieren, sonst ➡ Linkerfehler
    4. deklarierst Du einen pure virtual dtor, ist 2. nicht erfüllt. Daraus folgt 3.

    So hatte ich das ja auch verstanden, sah aber einen Widerspruch zu finix' Aussage "Der Compiler generiert ggf. implizit einen Destructor ..., ob (pure) virtual oder nicht.."

    Offensichtlich tut er es bei "=0" (und das ist der Fall, den wir hier seit dem ersetn Posting betrachten) eben NICHT.
    Natürlich kann sich das "ggf." auf etwas anderes als auf "implizit" beziehen, aber dann verstehe ich den Satz nicht besser.

    Sorry, wenn das Alles jetzt recht kleinlich wirkt, aber ich habe (offensichtlich) immer noch nicht begriffen, wo mein Verständnis des Ganzen falsch ist. Aber es muß ja falsch sein, weil sonst finix sich nicht die Mühe machte, mir das immer wieder zu bescheinigen. 😞
    Und ich würd's halt einfach gerne verstehen ....

    EDIT - Eine kleine Anmerkung (nur "comilerverifiziert") zu Deiner Liste habe ich nun aber doch: Sie hat nur deswegen einen Sinn, weil DToren eigentlich immer gebraucht werden (solange man überhaupt ein Objekt erzeugt - und sei es implizit). Normale Memberfunktionen müssen nicht implementiert sein - weil man auf ihre Benutzung verzichten kann.

    Gruß,

    Simon2.


Anmelden zum Antworten