"pure virtual destructor"



  • und wieso muss der abstrakte destruktor einen funktionskörper haben? ich könnte mir denken, dass dieser für den fall, dass eine abgeleitete klasse an eine variable vom abstrakten basistyp gebunden wird, dieser mit aufgerufen wird und er deshalb definiert sein muss. ist das so?



  • define_ schrieb:

    und wieso muss der abstrakte destruktor einen funktionskörper haben? ich könnte mir denken, dass dieser für den fall, dass eine abgeleitete klasse an eine variable vom abstrakten basistyp gebunden wird, dieser mit aufgerufen wird und er deshalb definiert sein muss. ist das so?

    Ganz einfach: Ein Destruktor muss immer einen Funktionskörper haben, weil der Destruktor beim Zerstören immer aufgerufen wird (bei Klassenhierarchien von aussen nach innen, sprich von most-derived nach least-derived). Das gilt für den automatisch erzeugten Destruktor, warum sollte es beim expliziten Destruktor anders sein?



  • /edit: Später als der Adelige, war auf define_ bezogen.

    Ich weiß nicht, was du damit sagen wolltest, aber es liegt einfach daran, dass die Basisklasse auch Daten hat, die aufgeräumt werden wollen, völlig unabhängig davon, ob sie abstrakt ist oder nicht. Und dafür braucht's halt nen Destruktor, der somit immer vorhanden ist. Der Compiler macht dafür sowas:

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


  • Wobei sich die Frage aufdrängt was ein rein virtueller dtor dann bringt, also wieso man das überhaupt machen kann.

    Die IMO einzig sinnvolle Antwort ist: Symmetrie. Wenns mit normalen Funktionen geht sollte es mit dem dtor auch gehen.



  • Der rein virtuelle Destruktor sorgt dafür, dass die Klasse abstrakt ist, auch wenn sie keine sonstigen rein virtuellen Funktionen enthält. Das ist doch auch was 🙂



  • LordJaxom schrieb:

    Der rein virtuelle Destruktor sorgt dafür, dass die Klasse abstrakt ist, auch wenn sie keine sonstigen rein virtuellen Funktionen enthält. Das ist doch auch was 🙂

    Das ist genau der Punkt: gerade wenn man verhindern will, dass eine Klasse, die ansonsten nur "normal"-virtuelle Funktionen hat, konkret benutzt wird, macht man den Destruktor pure virtual. Natürlich kann man dann einfach eine ansonsten leere Klasse von dieser abstrakten Klasse ableiten und erhält im Grunde das Selbe. Der Unterschied ist aber, dass man dann keine konkret benutzte Klasse innerhalb der Klassenhierarchie hat, sondern immer nur am Ende. (Ich mein Myers hätte dazu was in einem seiner effective C++ Bücher geschrieben.)
    Außerdem ist es häufig Konvention, zuerst die Konstruktoren, dann den Destruktor und erst danach andere Methoden zu deklarieren, damit ist das die erste Möglichkeit, eine Klasse als abstrakt zu kennzeichnen. Grade bei großen Klassen mit vielen Methoden ist das übersichtlicher als darauf zu vertrauen, dass jemand das "= 0" in Zeile 5276 bemerkt 😉



  • LordJaxom schrieb:

    Der rein virtuelle Destruktor sorgt dafür, dass die Klasse abstrakt ist, auch wenn sie keine sonstigen rein virtuellen Funktionen enthält. Das ist doch auch was 🙂

    Daran hab ich auch zuerst gedacht.
    Allerdings fällt mir einfach kein Anwendungsfall ein wo das Sinn machen würde.



  • hustbaer schrieb:

    Daran hab ich auch zuerst gedacht.
    Allerdings fällt mir einfach kein Anwendungsfall ein wo das Sinn machen würde.

    Nehmen wir an du hast eine Basisklasse die eine Handvoll virtuelle Funtkionen deklariert, bzw. sogar alle definiert weil sie sinnvolle default-Implementationen haben. Du willst ableitende Klassen nicht zwingen, irgendeine dieser Methoden zwangsweise zwangsweise zu überladen und ggf. nur die default-Implementation der Basisklasse aufzurufen, daher kannst du guten Gewissens kaum eine der Methoden pur virtuell machen. Der einzige Weg, die Klasse abstrakt zu machen ist daher der pur virtuelle Destruktor.
    Wie oben angedeutet könnte man jetzt bemerken dass man die Klasse dann auch so benutzen kann wie sie ist, da ja alle Implementierungen sinnvoll sind, allerdings wird eben oft gesagt, dass man nur Objekte von Klassen am Ende der Hierarchie konkret benutzen sollte.

    //Basisklasse, sollte man nicht unbedingt Objekte von erstellen -> abstrakt
    class AbstractActor
    {
    public:
      ~AbstractActor() {} = 0; //abstract
      virtual void DoSomeFoo()
      {
         Foo f;
         f.DoSomething();
      }
      virtual void DoSomeBar()
      {
         Bar b;
         b.DoSomething();
      }
    };
    
    //spezielle Version von DoSomeBar()
    class SpecialBarActor : public AbstractActor
    {
    public:
      virtual void DoSomeBar()
      {
         Bar b1, b2;
         b1.DoSomething();
         b2.DoSomething();
      }
    };
    
    class SpecialFooActor : public AbstracActor
    {
      /* Du kannst es dir denken...*/
    };
    
    //Standardklasse, Verhalten komplett defaultmaessig
    class NormalActor : public AbstracActor
    {};
    


  • hustbaer schrieb:

    Wobei sich die Frage aufdrängt was ein rein virtueller dtor dann bringt, also wieso man das überhaupt machen kann.

    Der "pure virtual destructor" garantiert, daß man über einen Basiszeiger dieser Klassenhierachie Objekte löschen kann. Wenn der Destructor nicht virtuell ist, dann wird der falsche Destruktor aufgerufen und das sorgt für Ärger. Wenn es eine abstrake Basisklasse ist, dann macht man den Destruktor halt "pure virtual". Eine Default Implementation kann man dem Destruktor immer noch mitgeben.



  • Eine Default Implementation kann man dem Destruktor immer noch mitgeben.

    Muss man. Nicht kann man.
    Simon



  • ~john schrieb:

    hustbaer schrieb:

    Wobei sich die Frage aufdrängt was ein rein virtueller dtor dann bringt, also wieso man das überhaupt machen kann.

    Der "pure virtual destructor" garantiert, daß man über einen Basiszeiger dieser Klassenhierachie Objekte löschen kann. Wenn der Destructor nicht virtuell ist, dann wird der falsche Destruktor aufgerufen und das sorgt für Ärger. Wenn es eine abstrake Basisklasse ist, dann macht man den Destruktor halt "pure virtual". Eine Default Implementation kann man dem Destruktor immer noch mitgeben.

    Manchmal gehst du mir kräftig damit auf den Sack dass du mir Dinge erklärst die ich a) schon weiss und die b) überhaupt nicht zum Thema gehören. Von virtuell oder nicht virtuell war nie die Rede, es ging um virtual pure oder bloss virtual.

    Und wenn die Klasse sowieso schon abstrakt ist braucht man den dtor nicht pure zu machen weil es genau garnix ändert.

    @pumuckl:
    Genau das meine ich ja, wenn man keine der Funktionen pure machen will, dann kann man die Klasse gleich instanzierbar lassen.

    Mir ist schon klar was passiert wenn man den dtor pure macht. Bloss wie gesagt sehe ich keinen Fall wo es IMO Sinn machen würde.



  • hustbaer schrieb:

    Manchmal gehst du mir kräftig damit auf den Sack dass du mir Dinge erklärst die ich a) schon weiss und die b) überhaupt nicht zum Thema gehören.

    Um bei Deiner blumigen Aussprache zu bleiben: Mir gehen Leute auf den Sack, die nicht aufmerksam sind.

    Die minimalste abstrakte Basisklasse einer polymorphen Klassenhierachie ist

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

    Wenn man das "pure" für den Destruktor verböte, dann wären keine leeren abstrakten Basisklassen mehr möglich. In "The Design an Evolution of C++" begründet Stroustrup, warum er damals die Entscheidung so traf wie er es tat.



  • simon.gysi schrieb:

    Muss man. Nicht kann man.
    Simon

    Man muß ihn implementieren, aber den möglichen leeren Destruktor würde ich nicht als "Default" bezeichnen.



  • hustbaer schrieb:

    @pumuckl:
    Genau das meine ich ja, wenn man keine der Funktionen pure machen will, dann kann man die Klasse gleich instanzierbar lassen.

    Wie erwähnt wird häufiger die Auffassung vertreten, dass polymorphe Basisklassen immer abstrakt sein sollten. Wenns woanders nicht möglich ist muss also der Dtor dafür herhalten.

    hustbaer schrieb:

    Und wenn die Klasse sowieso schon abstrakt ist braucht man den dtor nicht pure zu machen weil es genau garnix ändert.

    Wenn man sich an die Konvention hält, in abstrakten Klassen auf jeden Fall den Dtor pure virtual zu halten, hat man immer einen Anhaltspunkt wo man nachschauen kann.

    Man muss den pur virtuellen Dtor also nicht benutzen, man käme auch ganz ohne aus. Er bietet aber die Möglichkeit, Konventionen umzusetzen (Basisklasse = abstrakt, abstrakt = pv-Dtor).



  • Und was ist, wenn ich als Anwender eine abgeleitete Klasse mache, die schon mit dem Standarddestruktor sauber aufgeräumt wird (was bei dem RAII-lastigen Stil heutiger C++-Programme gar nicht so selten vorkommt)?
    Dann ist die obligatorische virtual ~Derived () {} nur Syntaxlärm.

    Ich sehe auch noch nicht so ganz den Sinn darin, den Destruktor rein virtuell zu machen.



  • .filmor schrieb:

    Und was ist, wenn ich als Anwender eine abgeleitete Klasse mache, die schon mit dem Standarddestruktor sauber aufgeräumt wird (was bei dem RAII-lastigen Stil heutiger C++-Programme gar nicht so selten vorkommt)?

    Dann brauchst Du genau garnichts unternehmen, weil der vom Compiler generierte Standard-Dtor ebenfalls eine Implementierung des pure virtual Destruktors ist.

    struct Abstract
    {
       virtual ~Abstract() = 0;
    };
    
    struct Concrete : Abstract
    {
    };
    
    Concrete instance;
    


  • .filmor schrieb:

    obligatorische virtual ~Derived () {} nur Syntaxlärm.

    Da in der Basis der Dtor schon virtuell ist ist es der automatisch gelieferte Dtor in der abgeleiteten Klasse auch, da ist also garnichts obligatorisch. Siehe NormalActor in meinem Beispiel oben, ganz ohne Lärm.



  • Na dann … 😉



  • Also die Frage nach dem Sinn stellt sich mir auch bissi ...

    der eigentliche zweck von pure virtual member, den ableiter drauf hinzuweisen, dass er selber was implementieren muss, greift ned, da destruktoren generiert werden wenn ned vorhanden.

    weiterhin, klassen, die technisch gesehen ned anders abstrakt gemacht werden koennen, aber abstrakt sein sollten, da faellt mir ned viel ein zu.
    - Leere interfaces wird wohl keiner bauen oder ?

    Bleibt irgendwie einzig das Argument, das man vielleicht in ner Hirarchie immer nur das ende instanzieieren sollte, und weiss ned ob das guter stil ist ... zumal es sich doch so einfach umgehen laesst. wenn ich an die def komme (sonst koennt ich ned instanzieren) kann ich auch ableiten ...

    Viele mir nur noch das thema RTTI, vielleicht in verbindung mit serializierung ein, aber dann waer das ableiten doch eigentlich nur ne weitere info, was sich irgendwie eleganter loesen lies ...

    Hat das wirklich swchon mal jemand gebraucht ?

    Ciao ...



  • RHBaum schrieb:

    Bleibt irgendwie einzig das Argument, das man vielleicht in ner Hirarchie immer nur das ende instanzieieren sollte, und weiss ned ob das guter stil ist ... zumal es sich doch so einfach umgehen laesst. wenn ich an die def komme (sonst koennt ich ned instanzieren) kann ich auch ableiten ...

    Mit dem Ableiten hast du dann aber eine Klasse geschaffen, die am Ende der Hierarchie steht, im Gegensatz zu deiner Basisklasse, auch wenn sie dieselbe Funktionalität hat. Siehe mein Beispiel oben.
    Es mag so aussehn als ob es nicht besonders sinnvoll ist, zwei Klassen mit derselben Funktionalität zu haben, von der die eine abstrakt ist und die andere nicht. Hier wurde schon das eine oder andere Mal gesagt "dann instantiiere ich doch einfach die Basisklasse". Es ist aber zu beachten, dass polymorphe Basisklassen und konkrete Klassen zwei verschiedene Aufgaben haben: Basisklassen bieten ein Interface das allen konkreten Klassen gemeinsam ist, konkrete Klassen sind dazu da, Objekte davon zu schaffen. Deshalb sollte man beides trennen, auch wenn mal eine konkrete Klasse exakt das Verhalten der Basisklasse haben sollte.
    Im Laufe der Entwicklung kann es vorkommen dass das Verhalten einer konkreten Klasse angepasst werden muss. Wenn ich konkrete Klasse und Basisklasse nicht getrennt habe, muss ich also eine neue Klasse ableiten und überall dort wo ich die Basisklasse konkret benutzt habe stattdessen die Ableitung benutzen. Das ist viel Textersetzung, bevor ich alles rekompiliere. Wenn ich dagegen von Anfang an die beiden Konzepte Basisklasse und konkrete Klasse getrennt halte, schreibe ich lediglich die konkrete Klasse um und rekompiliere - ohne überall Textersetzugen vorzunehmen. Noch komplizierter wirds, wenn man in verschiedenen Kontexten erstmal die selbe Funktionalität braucht wie die Basisklasse und dann in einem der Kontexte das Verhalten ändern muss - dann müsste man die Basisklasse an einzelnen Stellen (im Kontext wo sich das Verhalten ändert) ersetzen durch eine neue abgeleitete Klasse, an anderen Stellen (im anderen Kontext) aber nicht. Das Chaos ist vorprogrammiert. Stattdessen kann man von Anfang an für jeden Kontext eine leere Klasse von der Basisklasse ableiten und ist für solche Veränderungen bestens gewappnet.


Anmelden zum Antworten