new und Speicherfreigabe nach exception



  • Hi,

    was passiert mit dem Speicher der von new reserviert wird nach einer exception ? Folgendes Beispiel:

    void __fastcall TForm1::Button2Click(TObject *Sender)
    {
     try
      {
       Form2 = 0;  // Bin mir nicht sicher ob man das auf 0 setzen muss ?
       if ( Sender == Button2 )
         {
    	  Form2 = new TForm2(this);
    	  Form2->ShowModal();
    	 }
       else
    	 {
    	  Form2 = new TForm2(this, sNewTitel);
    	  Form2->ShowModal();
    	 }
       delete Form2;
      }
     catch( exception& e )
      {
       ShowMessage( e.what() );
       if ( Form2 ) delete Form2;  // Ist diese Abfrage überflüssig oder ist das zwingend notwendig ?
      } 
    }
    //---------------------------------------------------------------------------
    

    Wenn ich mit new ein neues Objekt (hier ein neues Formular) erzeuge und dann im neuen Objekt, z.B. gleich im C'tor eine Exception werfe, muss ich dann oben im catch()-Block prüfen ob noch ein Verweis auf das Objekt besteht und dann mit delete löschen, oder wird das Objekt automatisch gelöscht und der Speicher wieder freigegeben ?

    Im Debugger hab ich gesehen, dass nach einer Exception Form2 == 0 ist, aber kann ich da immer von aus gehen oder ist es besser immer auf 0 zu prüfen und dann zu deleten ? Und was ist mit dem reservierten Speicher, ist der dann wieder komplett freigegeben ?



  • Ja, du musst den Speicher - jedenfalls so wie du es machst - wieder manuell freigeben. Eleganter wäre es, das Ganze über RAII zu lösen.

    Außerdem empfehle ich die beiden Artikel aus dem Magazin zu Exceptions.

    Und die Überprüfung

    if(myPointer != 0){delete myPointer;}
    

    bringt nichts, das ist äquivalent zu

    delete myPointer;
    

    weil delete auf einem Nullpointer einfach nichts macht.

    Felix



  • Generell ist die Abfrage auf 0 vor einem 'delete' überflüssig, da 'delete' intern schon auf 0 überprüft. Es wird jedoch häufig so programmiert, daß man nach dem delete die Variable auf 0 setzt (um evtl. doppeltes Freigeben zu vermeiden).

    Und zur eigentlichen Frage bzgl. new und exceptions:
    Wenn beim Erzeugen eines Objektes im Konstruktor schon eine Exception geworfen wird, so wird ja die Variable (bei dir 'Form2') ja gar nicht erst gefüllt, d.h. die Exception wird ausgelöst und direkt in den catch-Zweig gesprungen (bzw. wenn dieser nicht vorhanden wäre, dann im Aufruf-Stack nach oben weitergereicht). Du brauchst dich dann um die Freigabe nicht mehr kümmern, da der angeforderte Speicher intern wieder gelöscht wird (sonst gäbe es ja bei jeder dieser Exceptions ein Speicherleck).

    Es könnte bei deinem Code aber auch passieren, daß erst im Form2->ShowModal() eine Ausnahme (excpetion) auftritt und dann müßtest du jedoch die Freigabe selber durchführen.

    Desweiteren solltest du bei lokalen Aufrufen einer anderen Klasse (bzw. Form) auch eine lokale Variable benutzen (der BCB legt zwar für jedes Formular eine Variable an, diese wird jedoch nur zwingend von der "Project.cpp" benötigt).

    Und zuletzt noch:
    du mußt auch zwischen den verschiedenen Exception-Klassen unterscheiden:
    - std::exception Basis-Klasse der Standard C++ Library
    - Exception Basis-Klasse der VCL

    Hier noch, wie ich es programmiert hätte:

    void __fastcall TForm1::Button2Click(TObject *Sender)
    {
      TForm2 form = 0; // lokale Variable
      try
      {
        if ( Sender == Button2 )
        {
          form = new TForm2(this);
        }
        else
        {
          form = new TForm2(this, sNewTitel); // s. Anmerkung unten
        }
    
        form->ShowModal();
      }
      catch( exception& e )
      {
        ShowMessage( e.what() );
      }
    
      delete form;
    }
    

    Die Klassen und Variablennamen solltest du natürlich aussagekräftiger benennen.
    Und bzgl. der beiden Konstruktoren bei TForm2 hätte ich einfach nur einen definiert und nachträglich dann die Eigenschaft 'NewTitle' gesetzt.

    Ich hoffe, ich habe damit deine Frage(n) zufriedenstellend beantwortet?

    P.S. Der generelle Hinweis auf RAII ist zwar gut, jedoch in diesem Fall nicht von Interesse. RAII benutzt man innerhalb von Konstruktoren, um dort angeforderten Speicher (bzw. Ressourcen) wieder freizugeben (wenn eine Exception passiert). Der Speicher für das eigentliche Objekt wird jedoch (wie ich oben schon geschrieben habe) selbständig bei einer möglichen Exception wieder freigegeben.



  • Wenn du komplexere Abhängigkeiten hast, lohnt sich das genannte RAII schnell einmal. Sonst musst du nämlich oftmals einzelne Fälle per if abfragen.
    Das new , das fehlschlägt, wird automatisch per delete freigegeben. Jedoch musst du dich selber um zuvor allokierten Speicher kümmern (wenn du zum Beispiel drei Speicheranforderungen hast und die dritte fehlschlägt, müssen die ersten beiden manuell freigegeben werden).

    Von daher empfehlen sich Smart Pointer - hier sollte boost::scoped_ptr reichen.



  • Th schrieb:

    P.S. Der generelle Hinweis auf RAII ist zwar gut, jedoch in diesem Fall nicht von Interesse. RAII benutzt man innerhalb von Konstruktoren, um dort angeforderten Speicher (bzw. Ressourcen) wieder freizugeben (wenn eine Exception passiert). Der Speicher für das eigentliche Objekt wird jedoch (wie ich oben schon geschrieben habe) selbständig bei einer möglichen Exception wieder freigegeben.

    Gut, wenn im Konstruktor eine Exception fliegt, wird der Speicher natürlich wieder freigegeben. Aber trotzdem kann man RAII doch nicht nur in Konstruktoren einsetzen. Ich hatte bei dem Post des OP nicht beachtet, dass Form2 keine lokale Variable war, deswegen war der Vorschlag dort wirklich unsinnig, aber wenn form wie in deinem Beispiel lokal ist, kann man RAII einfach dazu benutzen, für einen aufzuräumen (und spart sich damit das delete am Ende).

    Felix

    EDIT: Nexus war schneller 😉



  • Hallo zusammen,

    erstmal vielen Dank für Eure Hilfe. Ein paar Fragen habe ich noch:

    Th schrieb:

    Es könnte bei deinem Code aber auch passieren, daß erst im Form2->ShowModal() eine Ausnahme (excpetion) auftritt und dann müßtest du jedoch die Freigabe selber durchführen.

    D.h. einen eigenen try-catch-Block in Form2, oder meinst Du was anderes ?

    Th schrieb:

    Desweiteren solltest du bei lokalen Aufrufen einer anderen Klasse (bzw. Form) auch eine lokale Variable benutzen (der BCB legt zwar für jedes Formular eine Variable an, diese wird jedoch nur zwingend von der "Project.cpp" benötigt).

    Das habe ich jetzt noch nicht verstanden. Ich muss ja Form2 über Unit2 includieren, warum dann noch eine lokale Variable ?

    Mit

    TForm2 form = 0;
    

    bekomme ich folgende Fehlermeldung:
    [C++ Fehler] Unit1.cpp(34): E2459 Klassen im VCL-Stil müssen mit dem Operator new erstellt werden

    @Nexus und Phoemuex: Danke für den Hinweis, aber mit Smart Pointern und RAII habe ich mich noch nicht beschäftigt. Das muss ich mir alles noch reinziehen 😞 .



  • void __fastcall TForm1::Button2Click(TObject *Sender) 
    {
      if ( Sender == Button2 ) 
      { 
        boost::scoped_ptr<TForm2> Form2 = new TForm2(this); 
        Form2->ShowModal(); 
      } 
      else 
      { 
        boost::scoped_ptr<TForm2> Form2 = new TForm2(this, sNewTitel); 
        Form2->ShowModal(); 
      } 
    }
    


  • Shade Of Mine schrieb:

    void __fastcall TForm1::Button2Click(TObject *Sender) 
    {
      if ( Sender == Button2 ) 
      { 
        boost::scoped_ptr<TForm2> Form2 = new TForm2(this); 
        Form2->ShowModal(); 
      } 
      else 
      { 
        boost::scoped_ptr<TForm2> Form2 = new TForm2(this, sNewTitel); 
        Form2->ShowModal(); 
      } 
    }
    

    Nein, scoped_ptr<> ist für TForm und Derivate unangebracht:
    TCustomForm::Release()

    RAD Studio 2007-Dokumentation schrieb:

    Mit Release können Sie das Formular aus dem Speicher entfernen.

    Release gibt das Formular erst frei, nachdem die Ausführung der Ereignisbehandlungsroutinen des Formulars und seiner untergeordneten Komponenten beendet ist. Die Methode stellt auch sicher, dass alle Botschaften in der Ereigniswarteschlange des Formulars vor dessen Freigabe bearbeitet werden. Jede Ereignisbehandlungsroutine für das Formular oder für dessen untergeordnete Objekte sollte Release anstelle von Free (Delphi) oder Delete (C++) benutzen. Ansonsten kann ein Speicherzugriffsfehler auftreten.
    Anmerkung: Release gibt die Steuerung sofort an die aufrufende Routine zurück und wartet nicht, bis das Formular freigegeben wird.

    Im Übrigen entsteht kein Speicherleck, da der Owner von Form2, der im Konstruktor übergeben wird, das Objekt in jedem Fall freigibt - allerdings erst bei seiner eigenen Destruktion.



  • audacia schrieb:

    Nein, scoped_ptr<> ist für TForm und Derivate unangebracht:

    Dann ist das delete vom OP also auch falsch, oder?



  • Ja.


Anmelden zum Antworten