Der Destruktor wird nicht aufgerufen



  • Hallo,

    ich habe da ein seltsames Problem. Ich habe zu einer Klasse einen Destruktor definiert, so wie es halt sein muss, im Header

    class KlasseA {~KlasseA();};
    

    so wie auch in der Source

    KlasseA::~KlasseA() {...}
    

    . Dieser wird aber nicht aufgerufen und ich habe keine Ahnung wieso. Kennt jemand das Problem oder weiß woran es liegen könnte? Bin für jeden Tipp dankbar!

    mfg.



  • Ich hab keinen Plan, ob du C++ eigentlich kannst oder nicht...

    Deswegen nur 2 grundlegende Sachen:
    Falls du von der Klasse ableitest, mach den Destruktor virtual.
    Wenn du das Objekt auf dem Heap mit new erzeugst, mußt du es mit
    delete löschen.

    Gruß



  • In welchem Zusammenhang sollte er denn aufgerufen werden? Die Möglichkeiten, die mir spontan einfallen sind

    • delete vergessen
    • zwischendurch Exception geworfen
    • falls die Klasse von einer anderen polymorphen Klasse abgeleitet ist, bei letzterer den Destruktor nicht virtuell deklariert.

    Such dir eins aus oder zeig Code.



  • Stimmt...
    Die Exception hab ich vergessen 🙂



  • Also weder das eine noch das andere, die Klasse wird zwar von einer anderen abgelitten, jedoch besitzt der Vorfahre keinen Destruktor, und das erzeugte Objekt soll auch mit delete entfernt werden. Da ist eben das Problem,

    delete objektA;
    

    ruft den Destruktor nicht auf. Und bei den anderen Klassen funktioniert es auch.

    Ich arbeite seit etwas mehr als einem Monat mit C++.

    mfg.



  • class OGLSchriftart: public SpeicherbaresObjekt {
    //...
    public:
    //...
    	~OGLSchriftart();
    //...
    };
    
    OGLSchriftart::~OGLSchriftart() {
    
    // der hier stehende Code wird nicht aufgeruffen
    }
    
    delete oglsa;
    


  • class SpeicherbaresObjekt
    {
    public:
      virtual ~SpeicherbaresObjekt() { }
    };
    


  • 9taleFox schrieb:

    Also weder das eine noch das andere, die Klasse wird zwar von einer anderen abgelitten,

    Es heißt abgeleitet, leiden tut hier nichts. Höchstens der C++ Compiler 😉

    jedoch besitzt der Vorfahre keinen Destruktor,

    Das ist schlecht. Der Destruktor einer Basisklasse MUSS immer virtual sein, wenn du später mal mittels new Objekte aus der Klassenhierarchie anlegen und natürlich wieder mit delete freigeben willst (ohne dass dir dabei dein Programm in undefiniertes Verhalten abgleitet).

    Kleines Beispiel:

    #include <iostream>
    
    class Foo {
    public:
      Foo() {
        std::cout<<"Foo Ctor called\n";
      }
    
      virtual ~Foo() {
        std::cout<<"Foo Dtor called\n";
      }
    };
    
    class Bar : public Foo {
    public:
      Bar() {
        std::cout<<"Bar Ctor called\n";
      }
    
      virtual ~Bar() {
        std::cout<<"Bar Dtor called\n";
      }
    };
    
    int main() {
      Foo *f = new Bar;
      delete f;
      return 0;
    }
    

    Und jetzt schau dir mal die Ausgabe an.

    MfG

    GPC



  • Also ich habe jetzt die Vorschläge umgesetzt und doch geht es nicht, macht auch für den Fall keinen Sinn einen Destruktor anzulegen, da es nur einen Attribut besitzt und dieser muss nicht mit delete freigegeben werden.

    So sieht SpeicherbaresObjekt aus:

    class SpeicherbaresObjekt {
    protected:
    	string name;
    public:
    	SpeicherbaresObjekt(string& name) {this->name = name;}
    	string _name() {return name;}
    };
    

    Dubioser Weise wird das Objekt gelöscht, dass ist zum wahnsinnig werden.
    Und mein Compiler leidet weiter 🙂

    mfg.



  • Eine Klasse, von der (public) abgeleitet wird, sollte immer einen virtuellen Destruktor haben, selbst wenn der leer ist. Ansonsten kannst du Objekt der abgeleiteten Klasse nämlich nicht über einen Basisklassenzeiger wieder freigeben.



  • 9taleFox schrieb:

    ...jedoch besitzt der Vorfahre keinen Destruktor..

    Da täuschst Du Dich !
    JEDE Klasse besitzt einen Destruktor !
    Wenn Du selbst keinen deklarierst (in "class A { ....};"), dann erzeugt der Compiler Dir einen - und der ist nicht virtuell. Und das wiederum bedeutet, dass er nicht von dem einer abgeleiteten Klasse "overridden" wird !
    Wenn Du also willst, dass von Deiner Klasse abgeleitet wird, solltest/musst Du ihr einen virtuellen Dtor verpassen. Viele gute Programmierer sagen sogar, daß ein virtueller Dtor das Kennzeichen dafür ist, dass von dieser Klasse abgeleitet werden soll (bzw. bei Nichtvorhandensein: Nicht darf).....

    Bei "normalen Memberfunktionen" spielt es dabei übrigens erstmal keine Rolle, ob sie tatsächlich implementiert ist - Du brauchst nur eine Implementation, wenn sie auch aufgerufen wird.
    AAAABER: Ein Dtor wird IMMER aufgerufen (wenn ein entsprechendes Objekt instantiiert wird), deswegen braucht er auch immer eine Implementation (es sei denn, man will gerade darüber die Instantiierung verhindern).
    Und da eine Basisklasse innerhalb einer abgeleiteten Klasse auch instantiiert (und der Basis-Dtor automatisch aufgerufen) wird, kommt man da nicht drumherum.

    class BitteAbleiten {
    public:
        virtual ~BitteAbleiten() { std::cout << "~BitteAbleiten\n"; }
    };
    
    class GutAbgeleitet : public BitteAbleiten {
    public:
        ~GutAbgeleitet() { std::cout << "~GutAbgeleitet\n"; }
    };
    
    class BitteNichtAbleiten {
    public:
        ~BitteNichtAbleiten() { std::cout << "~BitteNichtAbleiten\n"; }
    };
    
    class SchlechtAbgeleitet : public BitteNichtAbleiten {
    public:
        ~SchlechtAbgeleitet() { std::cout << "~SchlechtAbgeleitet\n"; }
    };
    
    int main() {
        BitteAbleiten *gut = new GutAbgeleitet;
        BitteNichtAbleiten *schlecht = new SchlechtAbgeleitet;
    
        delete schlecht; // undefiniert; ruft bestenfalls nur ~BitteNichtAbleiten() auf
        delete gut; // ruft erst ~GutAbgeleitet() und dann ~BitteAbleiten() auf.
        return 0;
    }
    

    Kannst Du ja mal ausprobieren.....
    Das läuft auch identisch, wenn der BasisklassenDtor nur eine leere Implementation ("{}") hat - und genau DAS legt der Compiler für Dich an, wenn Du keinen eigenen Dtor deklarierst (aber leider nicht virtual).

    Gruß,

    Simon2.



  • Simon2 schrieb:

    AAAABER: Ein Dtor wird IMMER aufgerufen (wenn ein entsprechendes Objekt instantiiert wird), deswegen braucht er auch immer eine Implementation (es sei denn, man will gerade darüber die Instantiierung verhindern).
    Und da eine Basisklasse innerhalb einer abgeleiteten Klasse auch instantiiert (und der Basis-Dtor automatisch aufgerufen) wird, kommt man da nicht drumherum.

    Und selbst dann brauchst du eine Implementation für den Dtor - schließlich ruft der Dtor einer abgeleiteten Klasse automatisch den Basis-Dtor auf.

    (PS: Und neulich wurde ich belehrt, daß das delete schlecht in deinem Beispielcode als undefiniertes Verhalten gilt (auf Deutsch: das muß nichtmal den Basis-Dtor aufrufen))



  • CStoll schrieb:

    Simon2 schrieb:

    AAAABER: Ein Dtor wird IMMER aufgerufen (wenn ein entsprechendes Objekt instantiiert wird), deswegen braucht er auch immer eine Implementation (es sei denn, man will gerade darüber die Instantiierung verhindern).
    Und da eine Basisklasse innerhalb einer abgeleiteten Klasse auch instantiiert (und der Basis-Dtor automatisch aufgerufen) wird, kommt man da nicht drumherum.

    Und selbst dann brauchst du eine Implementation für den Dtor - schließlich ruft der Dtor einer abgeleiteten Klasse automatisch den Basis-Dtor auf....

    ... so wie ich im darauf folgenden Satz schrieb ... 😉

    Gruß,

    Simon2.



  • Ok, da habe ich mich schlecht ausgedrückt, mit “ ...jedoch besitzt der Vorfahre keinen Destruktor..“ habe ich gemeint, dass ich keinen definiert habe, dass einer vom Compiler erzeugt wird wusste ich auch, nicht aber, dass es nicht virtuell ist. Auch wenn es nicht virtuell ist so muss es aufgerufen werden, da der Destruktor des Erben aufgerufen wird und somit auch des Vorfahren. Das der Destruktor virtuell sein soll leuchtet mir ebenfalls ein.

    Hier der Ausschnitt in dem der Destruktor aufgerufen werden soll, das ist die einzige Stelle wo es geschieht:

    if (nackomen_Schriftart != NULL) {
    	HashtableIterator<OGLSchriftart>* hti = nackomen_Schriftart->items();
    	while (hti->hasMore()) {
    		OGLSchriftart* oglsa = hti->getNext();
    		delete oglsa;
    	}
    	delete hti;
    	delete nackomen_Schriftart;
    }
    

    Das dubiose ist eben, dass es die einzige Klasse dessen Speicher zwar freigegeben wird auch der Vorfahrklasse (wie ich es eben getestet habe), jedoch wird der Code im Destruktor nicht ausgeführt (der Vorfahrklasse schon).

    Der Destruktor:

    OGLSchriftart::~OGLSchriftart() {
    	HashtableIterator<OGLText>* oglti = texte->items();
    	while (oglti->hasMore())
    		((IEntferneSchriftart*)oglti->getNext())->entferneSchriftart();
    	delete oglti;
    	delete texte;
    	bild->nackomen_Schriftart->remove(name);
    }
    

    mfg.



  • Hi,

    mal eine Frage: Wie nutzt Du denn welchen Container ?
    STLContainer verwalten im allgemeinen Kopien, so dass Du für Polymorphie Zeiger in den Containern ablegen mußt ... ansonsten liegen da wirklich nur Basisobjekte.

    Ob's bei Dir so ist, weiß ich nicht, aber
    ...

    ...
    HashtableIterator<OGLSchriftart>...
    

    lässt mich daran denken.

    Gruß,

    Simon2.



  • Also der Container ist von mir geschrieben und hält nur Referenzen (die Objekte selbst ohne Kopien von dieser zu erzeugen).

    mfg.



  • Nur mal zur Orientierung: Ist 'OGLSchriftart' die Basisklasse oder die abgeleitete Klasse? Wenn ersteres, hängst du genau an dem Problem, das Simon oben beschrieben hat - da der Destruktor auf Basis des statischen Typs ausgewählt wird, erwischst du nur den Basis-Dtor, wenn du über einen Basis-Zeiger zugreifst.



  • Nein, das ist der 2 Fall:

    class SpeicherbaresObjekt {
    protected:
        string name;
    public:
        SpeicherbaresObjekt(string& name) {this->name = name;}
        virtual ~SpeicherbaresObjekt() {}  // nun jetzt auch virtuell
        string _name() {return name;}
    };
    
    class OGLSchriftart: public SpeicherbaresObjekt {
    //...
    public:
    //...
        virtual ~OGLSchriftart(); // nun jetzt auch virtuell
    //...
    };
    
    OGLSchriftart::~OGLSchriftart() {
    // der hier stehende Code wird nicht aufgeruffen
    }
    

    Wie gesagt ist das eben, das dubiose 😕

    mfg.



  • 😉

    Also ich denke, dass in dem Fall der Zeiger, auf den Du beim delete übergibst, tatsächlich auf eine Basisklasseninstanz zeigt.
    Entweder wirfst Du bereits ein "SpeicherbaresObjekt" in den Container oder der kopiert irgendwo doch implizit in ein solches....
    Schonmal den CopyCtor, operator=() und mögliche Konversionsoperatoren "privatisiert" ?

    Bei mir funktioniert jedenfalls auch der Umweg über "Basisklassen-Referenzen" prima:

    class BitteAbleiten {
    public:
        virtual ~BitteAbleiten() { std::cout << "~BitteAbleiten\n"; }
    };
    
    class GutAbgeleitet : public BitteAbleiten {
    public:
        ~GutAbgeleitet() { std::cout << "~GutAbgeleitet\n"; }
    };
    
    void deleteMe(BitteAbleiten* p) {
        delete p;
    }
    
    void doIt(BitteAbleiten& b) {
        deleteMe(&b);
    }	
    
    int main() {
        doIt(*new GutAbgeleitet);
        return 0;
    }
    

    Ergebnis wie erwartet:
    ~GutAbgeleitet
    ~BitteAbleiten

    Ein wenig überrascht mich, dass der Iterator (der Container auch ?) bei Dir für den Typen OGLSchriftart (also den abgeleiteten) instantiiert wird; eigentlich kenne ich das nur für Basistypen, wenn man Polymorphie nutzen will.... aber dazu weiß ich zuwenig von Deinem Container.

    Gruß,

    Simon2.



  • Nein, eben nicht wenn ich im Debugmodus bin und denn Code Schritt für Schritt durchgehe und mir die Variablen anschaue so zeigt oglsa auf eine Instanz von OGLSchriftart. In dem Programm habe ich noch einige andere Klassen die vom SpeicherbarenObekt erben und deren Destruktor auch aufgerufen wird und auch wenn es die Basisklasse währe so müsste der Destruktor der Abgeleiteten Klasse ebenfalls aufgerufen werden da ich dieses als virtual deklariert habe. Es ist halt die einzige Klasse die spinnt oder ich.

    Schonmal den CopyCtor, operator=() und mögliche Konversionsoperatoren "privatisiert" ?

    Nein.

    Ein wenig überrascht mich, dass der Iterator (der Container auch ?) bei Dir für den Typen OGLSchriftart (also den abgeleiteten) instantiiert wird; eigentlich kenne ich das nur für Basistypen, wenn man Polymorphie nutzen will.... aber dazu weiß ich zuwenig von Deinem Container.

    Ich iteriere den Container nur um die Instanzen zu entfernen.

    mfg.


Anmelden zum Antworten