Destructor nötig bei Zeiger in Klasse?



  • Hi,

    ich habe mal eine Frage, die die meisten von Euch wahrscheinlich mit Leichtigkeit beantworten können:
    Wenn ich einem Programm während der Laufzeit eine struct oder eine Klasse mit new erstelle und mit einem Zeiger darauf verweise, wird diese struct bzw. Klasse automatisch aus dem Speicher gelöscht, wenn ich keinen Zeiger mehr habe, der auf sie verweist?

    An meinem Beispiel:

    struct sfoo
    {
        int zahl1;
        int zahl2;
        int zahl3;
    };
    
    class cfoo
    {
    private:
        sfoo *psfoo;
    public:
        __fastcall cfoo();
    ...
    };
    
    __fastcall cfoo::cfoo()
    {
        psfoo = new sfoo;
    };
    

    Wenn ich die Klasse cfoo nun mit free freigeben würde, würde dann die von mir erzeugte Instanz von sfoo auch gelöscht werden oder muß ich das mit einem Destructor in cfoo machen?

    Vielen Dank für Eure Hilfe schon mal...



  • Geschieht nicht automatisch. Alles was du mit new anlegst, musst du irgendwann mit delete wieder freigeben.



  • Am besten im Destructor

    cfoo::~cfoo() 
    { 
       //nur löschen wenn noch vorhanden
       if(psfoo)
          delete psfoo; 
    };
    

    Kann ja sein das du den Zeiger irgendwo anders freigibst (auch mit delete).

    MFG Sigi



  • Die if-Abfrage kann man sich sparen, weil delete auch funktioniert (bzw. es passiert nichts) wenn der Zeiger 0 ist.



  • new und free zusammen geht nicht.

    Entweder im C-Style malloc/calloc und free Paar
    oder
    IM C++ Style mit new und delete Paar

    Für das Aufräumen bist du selber verantwortlich, Die Allokierungsfunktionen treten immer paarweise auf.

    Und da C++ die Destruktoren sinnvollerweise anbietet sollte man sie auch sinnvoll nutzen



  • Guter C++-Stil wäre IMHO, auf keinen Fall einen rohen Zeiger zu verwenden. Rohe Zeiger sind zum Beobachten da, nicht zum Besitzen. Nimm einen (const) auto_ptr, einen scoped_ptr (aus Boost, www.boost.org) oder etwas in der Art. Dann sparst du dir das Schreiben eines Destruktors und das automatisch generierte Kopieren/Zuweisen macht auch das richtige, kurz: Du ersparst dir eine Menge Arbeit beim Schreiben und Debuggen, weil du Objekte so verwendest, wie C++ es erwartet. Davon abgesehen sind new/delete-Paare bei mehr als einer Resource ohnehin nicht sicher. Der folgende, für Delphi-er (und scheinbar viele C++ler) ganz normale Code kann leaken:

    Klasse::Klasse()
    {
      res1 = new Resource;
      res2 = new Resource;
    }
    
    Klasse::~Klasse()
    {
      delete res2;
      delete res1;
    }
    

    delete/delete[] sollte man IMHO nur in Ausnahmefällen selber aufrufen.



  • ich würde nach delete,
    die zeiger immer NULL setzen - ok ist in einer abgeschlossenen klasse nicht nötig,
    aber wenn man sich sowas angewöhnt, dann läuft nix schief. d.h. kein zweites delete.



  • deswegen würde ich mir keine Sorgen machen. Bei einem guten Design löscht du nicht zweimal.
    operator voids Beispiel ist allerdings etwas anderes. viel kann man da ohne smart pointer nicht tun.



  • ich würde nach delete,
    die zeiger immer NULL setzen

    Ich würde vorschlagen Fehler lieber zu suchen und zu beseitigen statt sie nur zu verstecken.

    Null-Setzen aus *logischen* Gründen (Stichwort: Kardinalität 0..1) ist ok. Null-Setzen aus Angst vor doppel-delete ist eine ganz schlechte Idee.



  • Hallo,

    bitte erklaert mir, wo genau die Gefahr bei o. Code liegt.

    mfg
    v R



  • bitte erklaert mir, wo genau die Gefahr bei o. Code liegt.

    Meinst du das Beispiel von operator void? Der Code ist nicht Exceptionsicher.
    Fliegt nach der Allokation + Konstruktion von res1, aber vor der vollständigen Allokation + Konstruktion von res2 (also z.B. zuwenig Speicher oder Exception im Ctor von Resource) eine Exception, dann wird res1 niemals zerstört, d.h. weder der Dtor von res1 noch der von Klasse wird aufgerufen. C++ ruft Destruktoren nämlich nur für *vollständig* konstruierte Objekte auf.

    Daraus folgt:
    Eine Klasse sollte entweder *eine* Resource managen oder aber ihr Resource-Management an andere Klassen delegieren.

    Du hast hier zwei Möglichkeiten das Problem zu lösen.
    a) hässliche try-catch-Block-Lösung:

    Klasse::Klasse() 
    { 
        // res1 muss nicht in einen try-catch-Block.
        // Schlägt hier die Allokation oder Konstruktion fehl, 
        // sorgt das Laufzeitsystem für die *Speicherfreigabe*
        res1 = new Resource; 
        try
        {
           res2 = new Resource; 
        }
        catch(...)
        {
            delete res1;
        }
    } 
    
    Klasse::~Klasse() 
    { 
      delete res2; 
      delete res1; 
    }
    

    b) Verwendung von Resource-Managern

    // res1 und res2 jetzt nicht mehr Pointer sondern
    // SmartPointer, z.B. auto_ptr
    Klasse::Klasse() 
        : res1(new Resource)
        , res2(new Resource)
    {} 
    
    Klasse::~Klasse() 
    {}
    

    Die zweite Variante ist sicher. Schlägt die Konstruktion von res1 fehl, garantiert die Sprache, dass angeforderter Speicher für res1 automatisch freigegeben wird.
    Ist die Konstruktion von res1 erfolgreich, wird diese Resource nun von einem *vollständig* konstruiertem Objket verwaltet.
    Was immer jetzt noch passiert, dessen Dtor wird aufgerufen.



  • Danke fuer die Erklaerung.

    Ist es eigentlich ok, wenn man fuer dynamische Dinge nur smart pointer nutzt?

    Oder nur in Faellen, wie es dein Beispiel zeigt?

    mfg
    v R



  • Du meinst, wenn du nur eine Resource hast? Selbst dann würde ich immer smart-ptr (oder was eben passt) nehmen. Klar, Destruktoren sind schnell geschrieben, aber wenn man es auch kürzer haben kann und dann noch Kopieren/Zuweisen dazukommt? Wenn man smart-ptr nimmt, arbeiten die automatisch generierten Funktionen auf einmal nicht mehr gegen, sondern für einen und halten die Klassen schlank und damit fehlerfreier. Overhead haben smart-ptr auf gängigen Compilern keinen.



  • Wenn man smart-ptr nimmt, arbeiten die automatisch generierten Funktionen auf einmal nicht mehr gegen, sondern für einen

    Aber vorsicht mit auto_ptr. Wer vorher einen Copy-Ctor und einen op= brauchte, braucht diese beiden bei der Verwendung von auto_ptr mit 98.6% Wahrscheinlichkeit auch.



  • operator void schrieb:

    Du meinst, wenn du nur eine Resource hast? Selbst dann würde ich immer smart-ptr (oder was eben passt) nehmen. Klar, Destruktoren sind schnell geschrieben, aber wenn man es auch kürzer haben kann und dann noch Kopieren/Zuweisen dazukommt?

    Naja, Kopieren/Zuweisen?
    Wenn immer die Klasse die new macht, auch wieder delete macht, gibts da glaube ich keine Probleme.
    Und ich mache lieber einen Destruktor als #include <memory>. Erschwert nur das debuggen und lohnt sich imho meist nicht.



  • HumeSikkins schrieb:

    Aber vorsicht mit auto_ptr. Wer vorher einen Copy-Ctor und einen op= brauchte, braucht diese beiden bei der Verwendung von auto_ptr mit 98.6% Wahrscheinlichkeit auch.

    IMHO ist das dann eher ein Problem mit dem Verständnis von auto_ptr. Wenn ich ein auto_ptr-Member schreibe heißt das dann soviel wie "diese Klasse soll Move-Semantiken" (wie auto_ptr) haben. Wenn man sich mit einem auto_ptr Zuweisung und Kopieren schreibt, kann man auch gleich einen anderen smart-ptr nehmen 🙂 Naja, ist halt etwas blöd, dass ausgerechnet auto_ptr im Standard ist und der Rest noch nicht...

    DrGreenthumb schrieb:

    Wenn immer die Klasse die new macht, auch wieder delete macht, gibts da glaube ich keine Probleme.
    Und ich mache lieber einen Destruktor als #include <memory>. Erschwert nur das debuggen und lohnt sich imho meist nicht.

    class Buggy
    {
        X* ptr;
    public:
        Buggy() : ptr(new X) {}
        ~Buggy() { delete ptr; }
    };
    
    {
        Buggy a;
        Buggy b = a;
    } // Bumm!
    

    Also noch einen Kopierkonstruktor und einen Zuweisungsoperator schreiben. Und bei mehreren Zeiger-Membern dann langsam mit try..catch anfangen, damit nichts leaken kann, immer schön an vier Stellen was hinzufügen... Was spart man bitte damit? Ein #include...? Mit einem smart-ptr wäre die Klasse oben kürzer, fehlerfrei und wer den entsprechenden smart-ptr kennt, sieht auf den ersten Blick, was er für ein Verhalten von der Klasse erwarten kann. Und das Debuggen war bei mir auch nie ein Problem. Außerdem gibt es erstens weniger Sachen zu Debuggen, zweitens sind smart-ptr im Gegensatz zu rohen Pointern meistens noch durch assert()s abgesichert.



  • Ich kenn nur den auto_ptr und weiß jetzt nicht, wie ein smart-pointer das Problem hier lösen würde. Aber ich sag auch nicht, dass solche Pointerwrapper keine Daseinsberechtigung hätten. Wenn man's denn wirklich braucht.. Sowas wie deine Buggy-Klasse kommt, meiner wenigen Erfahrung nach, nur sehr selten vor. Deswegen halte ich es für unangebracht, generell jeden rohen Pointer durch sowas zu ersetzen. Zumindest momentan, wo man sich entweder von boost o.ä. abhängig macht, oder wo jeder seinen eigenen Brei programmieren muss.



  • Ich kenn nur den auto_ptr und weiß jetzt nicht, wie ein smart-pointer das Problem hier lösen würde.
    

    Man nimmt einen rohen Zeiger, wenn er nicht besitzen, sondern beobachten soll, und das automatische Kopieren und Zuweisen (aKuZ *abkürz*) ist korrekt.
    Man nimmt einen auto_ptr, wenn der Objektinhalt verschoben werden soll, und das aKuZ tut das.
    Man nimmt einen boost::scoped_ptr, wenn der Zeiger dem Objekt selbst gehört und unkopierbar ist, und das aKuZ gibt es gar nicht erst (korrekt).
    Man nimmt einen boost::shared_ptr, wenn die Objekte dieser Klasse sich das angezeigerte Objekt teilen sollen, und das aKuZ ist korrekt.
    Man nimmt einen selbstgeschrieben::copy_ptr (dessen operator= das angezeigerte Objekt des anderen Zeigers kopiert), wenn das angezeigerte Objekt jeder Objektinstanz selbst gehört, und das aKuZ ist korrekt.
    ...
    Wenn man aber nicht die Klasse nimmt, die für den entsprechenden Zweck gebaut wurde, muss man sich selber jedes Mal drum kümmern (und hat jede Menge Möglichkeiten, Fehler einzubauen).

    Sowas wie deine Buggy-Klasse kommt, meiner wenigen Erfahrung nach, nur sehr selten vor.
    

    Das aKuZ manuell auszuhebeln lernt man irgendwann (und regt sich meistens darüber auf, dass C++ sowas Gemeines hinter dem Rücken des Programmierers veranstaltet), aber auch das wird hier im Forum schon oft genug unterschlagen. Und dass Klassen mehrere Resourcen verwenden kommt IMHO oft genug vor, ab da wird es ohne smart-ptr wirklich unschön... 🙄

    Zumindest momentan, wo man sich entweder von boost o.ä. abhängig macht, oder wo jeder seinen eigenen Brei programmieren muss.
    

    boost::scoped_ptr und boost::shared_ptr sind schon für den nächsten Standard vorgesehen. Boost zu verwenden ist also nichts sonderlich Exotisches 🙂 Und sich die (bei mir) häufigsten zwei, boost::scoped_ptr und selbstgeschrieben::copy_ptr, selbst zu schreiben, dauert nicht lange - dafür kann man sich bei den restlichen Klassen dann zurücklehnen 🙂

    (*predig*)


Anmelden zum Antworten