Objekte löschen mit delete



  • SeppJ schrieb:

    Normale Zeiger sieht man in C++ eher selten.

    Normale Zeiger sollte man in gutem C++ Code möglichst selten sehen.

    Ebenso sollte man new möglichst selten sehen.

    Und fast gar nicht sollte man delete sehen (natürlich gibt es zu jedem new ein delete , aber das sollte in irgendeinem dtor versteckt werden).



  • if (MyClass) { delete MyClass; }

    uebrigens, die if Klausel ist vollkommen überfluessig.

    TMyClass *MyClass = NULL; 
    delete MyClass;
    

    sollte ohne murren durchlaufen !!!

    Ansonsten wie die Vorposter schon schreiben:
    new und delete nur in verzweifelten Situationen ohne anderen Ausweg! 😃

    Ciao ...



  • brotbernd schrieb:

    SeppJ schrieb:

    Normale Zeiger sieht man in C++ eher selten.

    Normale Zeiger sollte man in gutem C++ Code möglichst selten sehen.

    hast angst vor zeigern 🙄



  • Heimelchen schrieb:

    Ich war bisher immer davon ausgegangen, dass delete den Speicher freigibt und die Referenz auf NULL setzt. Offensichtlich ein Irrglaube. Muss ich den Zeiger manuell zurücksetzen, oder gibts da ne All-In-One-Lösung?

    Komische Frage. Du musst gar nichts. Du darfst einen ungültig gewordenen Zeiger aber nur zerstören, oder mit einem anderen gültigen Wert (inklusive Nullzeiger-Wert) füllen. Eine "All-In-One-Lösung" (Ich gehe mal davon aus, dass Du so etwas meinst:

    template<class T>
     void mydelete(T*& ptr) {delete ptr; ptr=0;}
    

    ) ist gar nicht so praktisch, wie Du wahrscheinlich glaubst. Mir fehlt so eine Funktionalität wirklich nicht und ich würde sogar behaupten, dass keiner so etwas braucht. Das liegt wohl daran, dass ich weder delete noch delete[] benutze. Und die seltenen Gelegenheiten, in denen ich new verwende sehen dann meist so aus:

    auto_ptr<Dings> ap (new Dings(...));
    

    ...wobei man sich natürlch darüber im Klaren sein muss, was Kopierkonstruktor und Zuweisungsoperator von auto_ptr<> für böse Sachen machen...

    kk



  • Hui, ist ja nen heißes Thema...

    Da ich eigentlich aus der C-Welt komme, hab ich absolut nix gegen Zeiger. Ganz im Gegenteil, mit Zeigern und Casts ist man extrem mächtig; wenn man's kann. Allerdings hab ich eher Zeiger auf bereits belegten Speicher verwendet, darum brauchte ich fast nie was freigeben.

    Intelligente Zeiger a'la auto_ptr sind zwar nett, erwartet aber eine Funktionen einen Onjektzeiger, kann ich ihm keinen auto_ptr geben. Will ich außerdem Objekte einer Klasse im Konstruktor erzeugen und im Destruktor freigeben, muss ich wohl new und delete verwenden.

    try/catch ist hier Quatsch, weil es nicht die Antwort auf meine Frage ist. Ich will nicht die Fehlerausgabe verhindern, sondern die Ursache beseitigen. Verwende auch try/catch nur, wenn es nicht anders geht. Sonst programmier ich lieber richtig... 😉

    Wie ihr vielleicht festgestellt habt, war das nur ein Auszug aus dem Code. Ich wollte ursprünglich den Zeiger als Indikator verwenden: ist er NULL, gibt es keinen gültigen Inhalt (z.B. weil die benötigte Datenbankverbindung fehlt). Ist er nicht NULL, ist der Inhalt gültig. Jetzt hab ich der Klasse einen Indikator dafür spendiert und erzeuge sie im Form-Konstruktor und lösche sie im Form-Destruktor.



  • Heimelchen schrieb:

    Will ich außerdem Objekte einer Klasse im Konstruktor erzeugen und im Destruktor freigeben, muss ich wohl new und delete verwenden.

    class A
    {
    private:
      B b; // <- das wird im Konstruktor automatisch 'erzeugt' und im Destruktor automatisch 'gelöscht'.
    };
    


  • Heimelchen schrieb:

    Intelligente Zeiger a'la auto_ptr sind zwar nett, erwartet aber eine Funktionen einen Onjektzeiger, kann ich ihm keinen auto_ptr geben.

    Könntest du dies nochmal in klaren deutsch formulieren?

    Heimelchen schrieb:

    Will ich außerdem Objekte einer Klasse im Konstruktor erzeugen und im Destruktor freigeben, muss ich wohl new und delete verwenden.

    Nicht zwangsweise, auch hier gehen normale Objekte ebenso. Und auch Smartpointer sind möglich.

    class A { /.../ };
    class B {
      private:
        A a_;
        std::tr1::shared_ptr<A> a2_;
        boost::scoped_ptr<A> a3_;
      public:
        B()
        : a_(),
          a2_(new A),
          a3_(new A)
        {}
    };
    

    Heimelchen schrieb:

    try/catch ist hier Quatsch...

    Und ist ohnehin meist auch nicht sinnvoll für new behandelbar (Und nur wenn es behandelbar ist, macht ein unmittelbarer try/catch-Block Sinn - egal was hier registrierte Trolle von sich geben).

    Heimelchen schrieb:

    Jetzt hab ich der Klasse einen Indikator dafür spendiert und erzeuge sie im Form-Konstruktor und lösche sie im Form-Destruktor.

    Könnte es sein, das du unter dem C++ Builder programmierst?



  • Heimelchen schrieb:

    Da ich eigentlich aus der C-Welt komme, hab ich absolut nix gegen Zeiger. Ganz im Gegenteil, mit Zeigern und Casts ist man extrem mächtig; wenn man's kann.

    Ich weiß nicht genau, worauf Du anspielst. Bedenke, dass eine Klasse relativ leicht seinen POD-Status verliert und damit viele Dinge aus der C-Trickkiste undefiniertes Verhalten hervorrufen würden.

    Heimelchen schrieb:

    Intelligente Zeiger a'la auto_ptr sind zwar nett, erwartet aber eine Funktionen einen Onjektzeiger, kann ich ihm keinen auto_ptr geben.

    Kommt auf die Aufgabe der Funktion an. Ich finde auto_ptr schön selbst dokumentierend. Wenn ich ein

    auto_ptr<abc> abstrakte_fabrik(...);
    

    sehe, dann weiß ich sofort, wer für das Löschen des Objekts zuständig ist.

    Heimelchen schrieb:

    Will ich außerdem Objekte einer Klasse im Konstruktor erzeugen und im Destruktor freigeben, muss ich wohl new und delete verwenden.

    Jein. Ausnahme-sichere Alternativen:

    class MyClass
    {
      Foo f; // <- kein Zeiger
      Bar b; // <- kein Zeiger
    ...
    };
    

    oder

    class MyClass
    {
      boost::scoped_ptr<Foo> pf;
      boost::scoped_ptr<Bar> pb;
    ...
    };
    

    Du solltest Dir schon einen anderen Programmierstil angewöhnen. Einen Stil, bei dem es keine Resourcen-Lecks geben kann, wenn irgendwo mal eine Ausnahme fliegt. Und einen Stil, bei dem nicht eine einzige Klasse gleich 5 Resourcen gleichzeitig "verwalten" muss in dem Sinne, dass ein delete oder close für alles im Destruktor benötigt wird.

    Heimelchen schrieb:

    try/catch ist hier Quatsch, weil es nicht die Antwort auf meine Frage ist. Ich will nicht die Fehlerausgabe verhindern, sondern die Ursache beseitigen.

    Re-Design.

    Heimelchen schrieb:

    Wie ihr vielleicht festgestellt habt, war das nur ein Auszug aus dem Code. Ich wollte ursprünglich den Zeiger als Indikator verwenden: ist er NULL, gibt es keinen gültigen Inhalt (z.B. weil die benötigte Datenbankverbindung fehlt). Ist er nicht NULL, ist der Inhalt gültig. Jetzt hab ich der Klasse einen Indikator dafür spendiert und erzeuge sie im Form-Konstruktor und lösche sie im Form-Destruktor.

    Wenn Du es für nötig hältst, den meisten Deiner Klassen einen Destruktor zu verpassen, dann machst Du wahrscheinlich etwas falsch.

    Gruß,
    kk



  • Ja, ich programmiere unterm C++-Builder.

    Dass ich aus der C-Welt komme und gern mit Zeigern arbeite, heißt nur genau das, was es heißt. Da kann man schnell über ein paar Strukturzeiger riesige Datenmengen in wenigen Zeilen Code verwalten. Ich verzichte deswegen aber nicht auf "normale" Instanzen von Objekten und Variablen oder sonst was.

    Wenn mein Programmierstil so schlecht ist, hätte ich gern zwei Beispiele zum bessermachen:
    1. Eine Klasse soll eine ADOConnection enthalten.
    2. Diese Klasse soll ein weiteres Objekt enthalten, das diese ADOConnection als Konstruktor-Parameter bekommt.

    Aktuelle Lösung:

    class TMyClass
    {
      private:
        TADOConnection *ADOConnection;
        TMyChild *MyChild;
      public:
        TMyClass();
        ~TMyClass();
    }
    
    TMyClass::TMyClass()
    {
      this->ADOConnection = new TADOConnection(NULL);
      this->MyChild = new TMyChild(this->ADOConnection);
    }
    
    TMyClass::~TMyClass()
    {
      delete this->ADOConnection;
      delete this->MyChild;
    }
    


  • Wie siehts damit aus?

    class TMyClass
    {
      private:
        TADOConnection ADOConnection;
        TMyChild MyChild;
      public:
        TMyClass();
    }
    
    TMyClass::TMyClass() : ADOConnection(0), MyChild(&ADOConnection)
    {
    }
    


  • 12 vs 21 😉



  • erkenn0r schrieb:

    12 vs 21 😉

    sicher gegen unsicher.
    was passiert denn wenn new TMyChild im Originalcode fehlschlägt?



  • Heimelchen schrieb:

    Aktuelle Lösung:

    class TMyClass
    {
      private:
        TADOConnection *ADOConnection;
        TMyChild *MyChild;
      public:
        TMyClass();
        ~TMyClass();
    }
    
    TMyClass::TMyClass()
    {
      this->ADOConnection = new TADOConnection(NULL);
      this->MyChild = new TMyChild(this->ADOConnection);
    }
    
    TMyClass::~TMyClass()
    {
      delete this->ADOConnection;
      delete this->MyChild;
    }
    

    Diese "Lösung" ist nicht Ausnahme-sicher und missachtet auch die Dreier-Regel. Wenn der Konstruktor von TMyChild eine Ausnahme wirft, hast Du ein Speicherleck. Du hast außerdem vergessen, Kopierkonstruktor und Zuweisungsoperator "auszuschalten" oder selbst bereitzustellen. Denn so besteht die Gefahr der Doppel-Löschung. Diese Probleme hast Du nicht, wenn Du den eigentlich nacheliegenden Ansatz (siehe Biolunars Beitrag) verfolgst.

    Heimelchen schrieb:

    Dass ich aus der C-Welt komme und gern mit Zeigern arbeite, heißt nur genau das, was es heißt.

    Das heißt wahrscheinlich erstmal, dass Deine C++ Programme nicht nach "idiomatischen C++" aussehen werden.

    kk



  • Es gibt unter dem Borland Builder ein paar unschöne Features, z.B. kann man keine Objekte, die von TObject abgeleitet sind, auf dem Stack erzeugen sondern nur dynamisch auf dem Heap. Daher sind manchmal solche Handstände notwending, bevor ich boost benutzt habe musste ich auch solche Dinge machen.
    Am besten verpackst du die Objekte in einen smart Pointer wie scoped_ptr oder shared_ptr aus boost/TR1, damit brauchst du keinen Destruktor mehr, in dem du irgendwelche Objekte wegräumen musst.



  • Heimelchen schrieb:

    Aktuelle Lösung:

    Meine Frage nach dem C++ Builder kam nicht von ungefähr (siehe hierzu auch den Beitrag von DocShoe, auch wenn ich absichtlich nicht Borland in der Bezeichnung verwende - mit Borland hat der C++ Builder nichts mehr zu tun).

    In deinem Fall ist eine dynamische Speicherverwaltung (was Smartpointer aber immer noch nicht ausschließt) leider zum teil nötig (zumindest bei den von TObjekt abgeleiteten Klassen).

    //...
    #include <boost/scoped_ptr.hpp> // Einer der möglichen Smartpointer
    
    class TMyClass
    {
      private:
        boost::scoped_ptr<TADOConnection> adoConnection_;
        boost::scoped_ptr<TMyChild> myChild_;
    
        // Zur Vereinfachung unterbinde ich mal Kopie/Zuweisung.
        TMyClass(TMyClass const &);
        TMyClass& operator=(TMyClass const &);
    
      public:
        TMyClass();
    };
    
    TMyClass::TMyClass()
    :   adoConnection_(new TADOConnection(NULL)),
        myChild_(new TMyChild(adoConnection_))
    {
    }
    

    Heimelchen schrieb:

    Dass ich aus der C-Welt komme und gern mit Zeigern arbeite, heißt nur genau das, was es heißt.

    Du solltest dir unter C++ einen anderen Programmierstil angewöhnen. Die Programmierung ist unter beiden Sprachen unterschiedlich.



  • Biolunar schrieb:

    Wie siehts damit aus?

    class TMyClass
    {
      private:
        TADOConnection ADOConnection;
        TMyChild MyChild;
      public:
        TMyClass();
    }
    
    TMyClass::TMyClass() : ADOConnection(0), MyChild(&ADOConnection)
    {
    }
    

    Dann compilier mal:
    "Klassen im VCL-Stil müssen mit dem Operator new erstellt werden!" Ist ja nich so, dass ich nicht schon nen bissl probiert hätte...

    Da ich von C (auf Mikrocontrollern) komme, mag ich auch aufgeblähten Code nicht wirklich. Es ist ja eine Krankheit des "guten Programmierstils" oder "moderner Sprachen", dass Resourcen einfach verballert werden, um die mögliche Inkompetenz des Programmieres auszugleichen. Alle Welt regt sich über aufgeblasene Programme auf, aber auf Resourcen achten möchte auch keiner (überspitzt gesagt).

    Warum soll ich meine Anwendung mit boost aufblasen (tolles Wortspiel)? Nur damit ich keine Pointer verwenden muss?



  • Heimelchen schrieb:

    Dann compilier mal:
    "Klassen im VCL-Stil müssen mit dem Operator new erstellt werden!" Ist ja nich so, dass ich nicht schon nen bissl probiert hätte...

    Den Hinweis hast du uns nicht gegeben! (indirekt haben das Leute hier abgeleitet, wg. dem erwähnten "Form-Konstruktor").

    Warum soll ich meine Anwendung mit boost aufblasen (tolles Wortspiel)? Nur damit ich keine Pointer verwenden muss?

    boost::scoped_ptr (oder auto_ptr) sind sehr dünne Wrapper um den eigentlichen Pointer. Da wird nicht viel zusätzlicher Speicher verbraucht. Ein shared_ptr macht Reference Counting, das ist zur Laufzeit teurer.
    Der Sinn dahinter: Zerstört wird automatisch! RAII rocks! Denn was ich noch mehr hasse als Programme, die vllt. 0,5% mehr Speicher brauchen sind solche, die stetig wachsen und irgendwann den Rechner lahmlegen 😛 Memory Leak sucks!



  • Heimelchen schrieb:

    Da ich von C (auf Mikrocontrollern) komme, mag ich auch aufgeblähten Code nicht wirklich. Es ist ja eine Krankheit des "guten Programmierstils" oder "moderner Sprachen", dass Resourcen einfach verballert werden, um die mögliche Inkompetenz des Programmieres auszugleichen. Alle Welt regt sich über aufgeblasene Programme auf, aber auf Resourcen achten möchte auch keiner (überspitzt gesagt).

    Da scheinst du ja beim C++ Builder genau richtig gelandet zu sein. Alle Objekte dynamisch erzeugen zu müssen, um sie über irgendeine TObject Basisklasse verwenden zu können (oder was weiß ich wozu) scheint mir doch hier die Oberblähung zu sein. Das ständige Anfordern von Speicher über new ist ziemlcih aufwendig im vergelich zum Erzeugen in vorhandenem Speicher oder aufm Stack.
    Im Fall vom scoped_ptr würde ich mal fast behaupten, dass do gut wie der gleiche Code generiert wird. Da passiert ja überhaupt nichts anderes als delete aufzurufen. Blos muss man das nicht explizit hinschreiben.



  • Heimelchen schrieb:

    Warum soll ich meine Anwendung mit boost aufblasen (tolles Wortspiel)? Nur damit ich keine Pointer verwenden muss?

    Nein, um Fehlerquellen zu reduzieren, die du mit normalen Zeigern hast. Ebenso wie der Code häufig sogar sicherer und kürzer wird (In meinem Beispiel ist der Destruktor z.B. gänzlich unnötig).

    Ganz davon abgesehen das du - wenn du schon von aufblasen etc. sprichts, auch die Initialisierungsliste der nachträglichen Zuweisung im Konstruktor vorziehen solltest.



  • brotbernd schrieb:

    Da scheinst du ja beim C++ Builder genau richtig gelandet zu sein. Alle Objekte dynamisch erzeugen zu müssen...

    Alle: Nein. Seine "TMyClass" zeigt zumindest keine Aspekte warum diese per new alloziert werden müsste. Davon abgesehen das ich das "T" mit Sicherheit nicht vor eigenen Klassen schreiben würde, die nicht nach der VCL-Notation aufgebaut sind (Was auch den Vorteil hat das man ziemlich genau sieht, bei welchen Klassen die C++ Spielregeln teilweise nicht ganz gelten - wie Konstruktorreihenfolgen, new-Zwang...).


Anmelden zum Antworten