20 Stimmen

Bewährte Praxis für verschachtelte TRY / FINALLY-Anweisungen

Hallo Was ist der beste Weg, um verschachtelte Try & finally-Anweisungen in Delphi zu tun?

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    cds3  : TClientDataSet;
    cds4  : TClientDataSet;
begin
  cds1      := TClientDataSet.Create(application );
  try
    cds2      := TClientDataSet.Create(application );
    try
      cds3      := TClientDataSet.Create(application );
      try
        cds4      := TClientDataSet.Create(application );
        try
        ///////////////////////////////////////////////////////////////////////
        ///      DO WHAT NEEDS TO BE DONE
        ///////////////////////////////////////////////////////////////////////
        finally
          cds4.free;
        end;

      finally
        cds3.free;
      end;
    finally
      cds2.free;
    end;
  finally
    cds1.free;
  end;
end;

Können Sie eine bessere Lösung vorschlagen?

3 Stimmen

Zuallererst: Übergeben Sie keinen Eigentümer, der nicht null ist, an Create, wenn Sie die Objekte ohnehin selbst freigeben wollen. Dies fügt nur eine Menge unnötigen Overhead hinzu.

0 Stimmen

Aaaaah, eine Reise in die Vergangenheit.

2 Stimmen

Ich weiß, 6 Monate zu spät - aber ich denke, dass dies technisch korrekt und der sichere Weg ist. Alle vorgeschlagenen Antworten haben mögliche Lecks oder benötigen Backup/Hilfsroutinen. Dieser Code wird einfach funktionieren, ohne Lecks.

31voto

skamradt Punkte 15128

Wie wäre es mit folgendem:

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    cds3  : TClientDataSet;
    cds4  : TClientDataSet;
begin
  cds1      := Nil;
  cds2      := Nil;
  cds3      := Nil;
  cds4      := Nil;
  try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);
    cds3      := TClientDataSet.Create(nil);
    cds4      := TClientDataSet.Create(nil);
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    freeandnil(cds4);
    freeandnil(cds3);
    freeandnil(cds2);
    freeandnil(Cds1);
  end;
end;

Dadurch bleibt es kompakt und es wird nur versucht, die Instanzen freizugeben, die erstellt wurden. Es besteht wirklich keine Notwendigkeit, die Verschachtelung durchzuführen, da JEDER Fehler zu einem Rückfall auf die letzte Instanz führt und alle Aufräumarbeiten in dem von Ihnen angegebenen Beispiel durchgeführt werden.

Ich persönlich versuche, innerhalb derselben Methode keine Verschachtelungen vorzunehmen... mit der Ausnahme, dass ein try/try/except/finally-Szenario vorliegt. Wenn ich finde, dass ich verschachteln muss, dann ist das für mich ein guter Zeitpunkt, um über eine Umstrukturierung in einen anderen Methodenaufruf nachzudenken.

EDIT Ein bisschen aufgeräumt dank der Kommentare von mghie y utku .

EDIT die Objekterstellung so geändert, dass sie nicht auf die Anwendung verweist, da dies in diesem Beispiel nicht erforderlich ist.

1 Stimmen

Sie müssen cds2, cds3 und cds4 mit nil initialisieren, sonst erhalten Sie AVs beim Freigeben der (ungültigen) Objekte, wenn die Erstellung von cds1 fehlgeschlagen ist. Nur bei Strings und Schnittstellenzeigern kann davon ausgegangen werden, dass sie leer sind, wenn sie als Stack-Variablen verwendet werden.

0 Stimmen

Sie brauchen keine "if Assigned(cdsX)"-Prüfungen, FWIW.

0 Stimmen

Bitte beachten Sie auch, dass dieser Code anfällig für Speicherlecks ist, wenn TClientDataSet.Destroy() Exceptions auslöst. Siehe meine eigene Antwort für Code, der so geschrieben werden kann, dass er auch in diesem Fall sicher ist.

18voto

mghie Punkte 31618

Ich würde etwa so vorgehen:

var
  Safe: IObjectSafe;
  cds1 : TClientDataSet;
  cds2 : TClientDataSet;
  cds3 : TClientDataSet;
  cds4 : TClientDataSet;
begin
  Safe := ObjectSafe;
  cds1 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds2 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds3 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds4 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  ///////////////////////////////////////////////////////////////////////
  ///      DO WHAT NEEDS TO BE DONE
  ///////////////////////////////////////////////////////////////////////

  // if Safe goes out of scope it will be freed and in turn free all guarded objects
end;

Für die Implementierung der Schnittstelle siehe ce Artikel, aber Sie können etwas Ähnliches leicht selbst erstellen.

EDIT:

Mir ist gerade aufgefallen, dass in dem verlinkten Artikel Guard() eine Prozedur ist. In meinem eigenen Code habe ich Guard()-Funktionen überladen, die TObject zurückgeben, obiger Beispielcode geht von etwas Ähnlichem aus. Natürlich mit Generika viel besser Code ist jetzt möglich...

EDIT 2:

Wenn Sie sich fragen, warum try ... finally in meinem Code vollständig entfernt ist: Es ist unmöglich, die verschachtelten Blöcke zu entfernen, ohne die Möglichkeit von Speicherlecks (wenn Destruktoren Ausnahmen auslösen) oder Zugriffsverletzungen einzuführen. Daher ist es am besten, eine Hilfsklasse zu verwenden und die Referenzzählung der Schnittstellen vollständig zu übernehmen. Die Hilfsklasse kann alle von ihr bewachten Objekte freigeben, selbst wenn einige der Destruktoren Ausnahmen auslösen.

1 Stimmen

Nette Implementierung, +1 für die Verwendung, aber es eliminiert das Try finally vollständig, was für dieses Beispiel gut genug sein könnte.

0 Stimmen

Ich sehe try ... finally als wenig mehr als eine Krücke, die notwendig ist, weil RAII in Delphi ohne Schnittstellen nicht möglich ist. Hätte Delphi Stack-allocated-Objekte, gäbe es keinen großen Bedarf für try ... finally. Dennoch könnte es zusammen mit der SafeGuard-Schnittstelle verwendet werden.

0 Stimmen

@mghie: Außer in Fällen, in denen der Destruktor das Handle schließt oder andere Ressourcen freigibt, was ziemlich häufig vorkommt.

4voto

mghie Punkte 31618

Es gibt noch eine andere Variante des Codes ohne verschachteltes Try ... schließlich ist mir das gerade eingefallen. Wenn Sie die Komponenten nicht mit dem AOwner-Parameter des Konstruktors, der auf Null gesetzt ist, erstellen, dann können Sie einfach die Lebenszeitverwaltung nutzen, die Ihnen die VCL kostenlos zur Verfügung stellt:

var
  cds1: TClientDataSet;
  cds2: TClientDataSet;
  cds3: TClientDataSet;
  cds4: TClientDataSet;
begin
  cds1 := TClientDataSet.Create(nil);
  try
    // let cds1 own the other components so they need not be freed manually
    cds2 := TClientDataSet.Create(cds1);
    cds3 := TClientDataSet.Create(cds1);
    cds4 := TClientDataSet.Create(cds1);

    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////

  finally
    cds1.Free;
  end;
end;

Ich bin ein großer Verfechter von kleinem Code (wenn er nicht zu verworren ist).

1 Stimmen

Das stimmt nicht ganz. Es spielt keine Rolle, ob eine Komponente, die ich manuell freigebe, einen Besitzer hat oder nicht - die Zerstörung des Objekts löst Notification() beim Besitzer aus, so dass der Absender aus seiner Liste der besessenen Objekte entfernt wird. Aber ich stimme zu, die Übergabe von nil als AOwner funktioniert genauso gut.

0 Stimmen

Bei der zweiten Bemerkung stimme ich allerdings zu. cds1 nicht freizugeben (d.h. die Anwendung dies beim Herunterfahren der Anwendung tun zu lassen) ist kaum besser als ein Speicherleck.

0 Stimmen

Schön, aber die IObjectSafe ist besser IMO, weil es TObjects als auch TComponents behandelt.

3voto

user81126 Punkte 111

Wenn Sie diesen (IMO) hässlichen Weg gehen wollen (Gruppenbehandlung mit Initialisierung auf nil, um zu wissen, ob das Freigeben erforderlich ist), MÜSSEN Sie zumindest garantieren, dass Sie nicht zulassen, dass eine Ausnahme in einem der Destruktoren das Freigeben der restlichen Objekte verhindert.
Etwa so:

function SafeFreeAndNil(AnObject: TObject): Boolean;
begin
  try
    FreeAndNil(AnObject);
    Result :=  True;
  except
    Result := False;
  end;
end;

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    IsOK1 : Boolean;
    IsOK2 : Boolean;
begin
  cds1      := Nil;
  cds2      := Nil; 
 try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);    
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    IsOk2 := SafeFreeAndNil(cds2);    // an error in freeing cds2 won't stop execution
    IsOK1 := SafeFreeAndNil(Cds1);
    if not(IsOk1 and IsOk2) then
      raise EWhatever....
  end;
end;

2voto

Charles Faiga Punkte 11572

Es gibt ein gutes Video auf Ausnahmen in Konstruktoren und Destruktoren

Sie zeigt einige schöne Beispiele, wie zum Beispiel:

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
begin
  cds1      := Nil;
  cds2      := Nil; 
 try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);    
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    freeandnil(cds2);    //// what has if there in an error in the destructor of cds2
    freeandnil(Cds1);
  end;
end;

Was ist, wenn es einen Fehler im Destruktor von cds2 gibt?

Cds1 wird nicht vernichtet

EDIT

Eine weitere gute Quelle ist :

Jim McKeeth ausgezeichnetes Video über Verzögerte Ausnahmebehandlung in Codebereich III, wo er über Probleme bei der Behandlung von Ausnahmen im Finally-Block spricht.

0 Stimmen

Ich habe mich zu diesem Problem bereits in der von Ihnen akzeptierten Antwort geäußert. Wenn Sie die verschachtelten finally-Blöcke loswerden wollen, verwenden Sie ein Hilfsobjekt, das alle bewachten Objekte freigibt. Dies kann dann auf eine ausnahmesichere Weise geschehen. Beachten Sie, dass TComponent.DestroyObjects() das gleiche Problem hat!

0 Stimmen

Ich habe es gerade versucht, und tatsächlich führt das Auslösen einer Ausnahme im Destruktor einer Komponente, die zu einem sekundären Formular gehört, zu Zugriffsverletzungen, wenn die Anwendung heruntergefahren wird. Es ist also wahrscheinlich am besten, alle Möglichkeiten für Ausnahmen in Destruktoren zu eliminieren.

0 Stimmen

@mghie: Schauen Sie sich das Delayed Exception Handling an, das er verlinkt hat. Ich habe es speziell geschrieben, um Ausnahmen in Destruktoren zu behandeln, denn wenn der Code 3rd Party ist, kann man ihn nicht immer ändern.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X