Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor? (https://www.delphipraxis.net/174250-der-konstruktor-ist-abgestuerzt-wie-gehe-ich-im-destruktor-am-besten-vor.html)

Der schöne Günther 11. Apr 2013 14:31

Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Es ist einer der Fälle, der nicht passieren sollte. Ähnliches habe ich schonmal in Sachen Beenden abgestürzter Threads angestoßen ("Wie beende ich einen TThread freundlich und notfalls gewaltsam?") - Trotzdem möchte ich mich darum kümmern.

Folgende Annahme: Der Konstruktor eines Objekts läuft nicht durch, irgendwo mittendrin fliegt er mit einer Exception raus. Wenn ich es richtig verstanden habe, wird nun das Exception-Objekt erzeugt, der Destruktor des Objekts aufgerufen und anschließend die Exception "nach oben gereicht".

Es geht um den Destruktor: Hier muss man nun aufpassen, keine Objekte zu zerstören die vom Konstruktor noch nicht angelegt worden sind.

Wie handelt Ihr hier?
  • Bei jeder einzelen Freigabe prüfen, ob es überhaupt etwas freizugeben gibt?
  • Vielleicht sogar eine Instanzvariable die angibt, ob der Konstruktor einwandfrei durchgelaufen ist um darauf im Destruktor reagieren zu können?
  • Oder noch ganz andere Dinge die zu beachten sind?

Ich bin gespannt 8-)

DeddyH 11. Apr 2013 14:35

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Davon ausgehend, dass es sich bei den angesprochenen Objekten um private Felder der eigenen Klasse handelt, musst Du nichts weiter beachten. Diese sind initial nil, wenn Du sie also im Destruktor mit Free freigibst, passiert nichts, da Free erst auf nil prüft und dann erst Destroy aufruft. Daraus folgt: was nicht da ist, wird auch nicht versucht freizugeben.

Der schöne Günther 11. Apr 2013 14:42

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Tatsächlich. Ich bin durcheinandergekommen da der Debugger bei einer Exception im Konstruktor einen mit F7 nicht mehr durch den Destruktor wandern lässt soweit man nicht explizit einen Haltepunkt dort hineinsetzt.

Ich rede wirres Zeug, belassen wir es damit ;-)

ConnorMcLeod 11. Apr 2013 15:50

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Vllt bin ich jetzt auf dem falschen Dampfer, aber was spricht gegen
Delphi-Quellcode:
FreeAndNil
?

DeddyH 11. Apr 2013 15:57

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Im Prinzip nichts, aber Free genügt in diesem Falle auch.

Klaus01 11. Apr 2013 16:03

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
.. freeAndNil erzeugt eine Exception wenn die freizugebene Instanz nicht mehr existiert.

Grüße
Klaus

flipdascript 11. Apr 2013 16:07

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Zitat:

Zitat von Klaus01 (Beitrag 1211163)
.. freeAndNil erzeugt eine Exception wenn die freizugebene Instanz nicht mehr existiert.

Grüße
Klaus

Aber nur, wenn die Referenz <> nil ist. Das ist keine Einschränkung von FreeAndNil. In dem Fall würde es bei Referenz.Free genauso knallen.

Aphton 11. Apr 2013 16:35

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Delphi-Quellcode:
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TMyBuggyObject = class
    PrivObject1: TObject;
    PrivObject2: TObject;
    constructor Create;
    destructor Destroy; override;
  end;

constructor TMyBuggyObject.Create;
begin
  Writeln('TMyBuggyObject.Create(): Allocating object1');
  PrivObject1 := TObject.Create;
  Writeln('TMyBuggyObject.Create(): Throwing exception');
  raise Exception.Create('TMyBuggyObject.Constructor - selfdestruct');
  Writeln('TMyBuggyObject.Create(): Allocating object2');
  PrivObject2 := TObject.Create;
end;

destructor TMyBuggyObject.Destroy;
begin
  Writeln('TMyBuggyObject.Destroy(): Im being called.');
  Writeln('TMyBuggyObject.Destroy(): PrivObj1=0x' + IntToHex(integer(PrivObject1), 8));
  Writeln('TMyBuggyObject.Destroy(): PrivObj2=0x' + IntToHex(integer(PrivObject2), 8));
  Writeln('TMyBuggyObject.Destroy(): Freeing both!');
  PrivObject2.Free;
  PrivObject1.Free;
  inherited;
end;

procedure main;
var
  Instance: TMyBuggyObject;
begin
  Instance := NIL;
  try
    Instance := TMyBuggyObject.Create;
    try
      Writeln('Maincode: No exception!');
    finally
      Instance.Free;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Writeln('Address of instance:'#9'0x', IntToHex(integer(Instance), 8));
  Readln;
end;

begin
  ReportMemoryLeaksOnShutdown := True;
  main;
end.
Code:
TMyBuggyObject.Create(): Allocating object1
TMyBuggyObject.Create(): Throwing exception
TMyBuggyObject.Destroy(): Im being called.
TMyBuggyObject.Destroy(): PrivObj1=0x00520D10
TMyBuggyObject.Destroy(): PrivObj2=0x00000000
TMyBuggyObject.Destroy(): Freeing both!
Exception: TMyBuggyObject.Constructor - selfdestruct
Address of instance:   0x00000000

Uwe Raabe 11. Apr 2013 16:42

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Was willst du uns mit dem Code jetzt sagen?

DeddyH 11. Apr 2013 16:43

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Ich dachte schon, ich sei der Einzige, der sich das fragt :)

Der schöne Günther 11. Apr 2013 16:50

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1211169)
Was willst du uns mit dem Code jetzt sagen?

Zitat:

Zitat von DeddyH (Beitrag 1211170)
Ich dachte schon, ich sei der Einzige, der sich das fragt :)

Das Erkennen wird für Euch erst beginnen, wenn Ihr als Erkennende vom zu Erkennenden Abstand nehmt.

Seid Ihr soweit? Gut. Ich denke es ging um meine erste (nebensächliche Frage), ob wirklich erst der Destruktor aufgerufen und dann die Exception nach oben gereicht wird. Das hier scheint zu bestätigen: Ja, das ist so.



Um die Wogen der Verwirrung, was ich eigentlich wollte, noch etwas zu glätten: Ich hatte irgendwo im Destruktor eines Objekts (das seinen Konstruktor nicht vernünftig auf die Reihe bekam) einen Thread terminiert (myThread.Terminate()) der allerdings bislang noch garnicht initialisiert werden konnte.
Anstatt nun das zu erkennen (ich hatte vom zu Erkennenden als Nicht-Erkennender wohl noch nicht genug Abstand genommen) habe ich erst mal ein wirres Thema hier im Forum erstellt. Vielleicht hilft etwas weniger Kaffee in Zukunft dagegen...

Aphton 11. Apr 2013 16:52

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Ich war mir nicht ganz sicher, ob der Destruktor überhaupt aufgerufen wird
(klar logisch ists, dass er aufgerufen wird - wenn man mal drüber nachdenkt xD)
Weiters zeigt der Code einfach nur, dass es eben zu keinen Problemen im Destruktor
kommt, weil free nachprüft. Im Grunde wollt ich damit indirekt zeigen, dass er seine
Frage selber auch beantworten hätte können.. Aber pschtt..

DeddyH 11. Apr 2013 16:53

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Das muss alten Leuten doch auch mal gesagt werden *mitdemKrückstockwink* :mrgreen:

sx2008 11. Apr 2013 16:59

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Also generell sollte man einen Konstruktor so schreiben das er keine Exception auslösen kann!!!
Das heisst also keine Resouren (mit Ausnahme von Speicher) belegen, keine Dateioperationen, keine problematischen Windows-API-Aufrufe, usw.

Die Folge ist aber, dass man dann evtl. ein unvollständig initialisiertes Objekt erhält.
Diesen Zustand kann man aber dadurch abhelfen, dass man eine Setup-Methode hat, die das Objekt vollständig initialisiert:
Delphi-Quellcode:
var
  meinobj : TMyClass;
begin
  meinobj := TMyClass.Create;
  try
    meinobj.Setup;
    mainobj.MachwasDamit;
  finally
    meinobj.Free;
  end;
Dies hat aber den unschönen Effekt, dass man mehr Schreibarbeit hat und dass man den Aufruf von Setup auch mal vergessen kann.
Dem kann aber abgeholfen werden, indem man die Setup-Methode so schreibt:
Delphi-Quellcode:
type
  TMyClass=class(TObject)
  private
    FSetupDone : Boolean;
  protected
    procedure Setup;virtual;
  public
    procedure MachWasDamit;
  end;

procedure TMyClass.Setup;
begin
  if not FSetupDone then
  begin
    // hier der Code der nicht in den Konstruktor soll, weil er potentiell eine Exception
    // auslösen kann
    ...
    FSetupDone := True;
  end;
end;

procedure TMyClass.MachWasDamit;
begin
  Setup; // wird jeder Methode von TMyClass aufgerufen, damit sichergestellt wird
  // dass das Objekt vollständig initialisert ist

  // hier der Code von MachWasDamit
  ..
end;
Ok, diese Methode bedeutet mehr Schreibarbeit und auch eine minimale Performanceverschlechterung aber man vermeidet so eine Exception im Konstruktor mit allen seinen hässlichen Konsequenzen.

Es gibt auch noch andere Möglichkeiten um Code im Konstruktor zu vermeiden, der potentiell eine Exception auslösen kann.
Stichwort: Dependeny Injection

Angenommen ein Thread soll in eine Datei loggen.
Im naiven Ansatz würde man das so machen:
Delphi-Quellcode:
constructor TMyThread.Create;
begin
  inherited Create;
  FLogger := TLogger.Create;
  // Gefahr!
  // hier kann eine Exception ausgelöst werden (z.B. keine Schreibrechte auf der Lodatei)
  FLogger.OpenLogfile('C:\log.txt');
end;
Bei der Dependency Injection würde die Threadklasse verlangen, dass ein geöffnetes (!) Logobjekt übergeben wird:
Delphi-Quellcode:
constructor TMyThread.Create(logger:TLogger);
begin
  inherited Create;
  FLogger := logger; // das ist sicher; keine Exception möglich
end;

ConnorMcLeod 11. Apr 2013 17:02

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Daß der Destructor zur Gänze durchlaufen wird bevor die Exception an die Oberfläche kommt.

Aphton 11. Apr 2013 17:30

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Also ich finde, dass es eig. ok ist, im Konstruktor die Objekte zu erzeugen. Ein absolut wichtiger Punkt beim sauberen Design - neu aufgeschnapt übrigens xD - jedoch ist, dass man nichtsa außer Instanzierung im Konstruktor betreibt - also keine Initialisierung über Methoden oder dergleichen.

Das Problem ist nämlich, wenn man bsp. eine virtuelle Methode zum initialisieren verwendet, kann und wird die von erbenden Klassen überschrieben werden. Üblicherweise hat man aber keine Kontrolle über die Implementierung dieser - dh. die überschreibende (initialisier-) Methode kann fälschlicherweise auf Felder/Objekte der Klasse zugreifen, die jedoch noch nicht initialisiert worden sind, weil die Methode eben erst im Konstruktor aufgerufen wird.

sahimba 11. Apr 2013 18:09

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Zitat:

Zitat von sx2008 (Beitrag 1211174)
Diesen Zustand kann man aber dadurch abhelfen, dass man eine Setup-Methode hat, die das Objekt vollständig initialisiert

Das würde ich (fast) als klassisches Anti-Pattern bezeichnen.

Der schöne Günther 12. Apr 2013 07:46

AW: Der Konstruktor ist abgestürzt - Wie gehe ich im Destruktor am besten vor?
 
Um ganz ehrlich zu sein: Mir ist so ein Pseudo-Konstruktor, dessen eigentliche Arbeit dann im Endeffekt wieder von einer ganz normalen Instanzmethode übernommen wird, auch nicht wirklich sympathisch. Bislang sehe ich nur, dass man es besser nicht tun sollte, aber einen wirklichen Grund habe ich nicht.

Noch vor der ersten Zeile im Konstruktor sind Instanzmember bereits mit einem Standardwert initialisiert. Zahlen sind Null, Objekte nil und Strings sind '' (richtig?). Meine Frage ganz am Anfang kam von Verwirrung und der falschen Annahme, das sei nicht der Fall.


In meinem ganz konkreten Fall hat der Konstruktor die Exception sogar absichtlich geworfen: Unter den gegebenen Umständen war eine Erstellung eines sinnvollen Objekts nicht möglich. Kurioserweise war die Erstellung dieses Objekts (auf Umwegen) wiederum Bestandteil des Konstruktor eines anderen Objekts.
Wo liegt nun der Unterschied, wäre der Konstruktor hier nicht mit einer Exception ausgestiegen sondern hätte ein quasi nutzloses Objekt mit einem Flag "ichBinNichtZuGebrauchen:Bool" ausgespuckt? Im Endeffekt ist das doch noch aufwändiger und unübersichtlicher (und wahrscheinlich sogar fehleranfälliger) zu handhaben als eine "stinknormale" Exception.


Alle Zeitangaben in WEZ +1. Es ist jetzt 04:07 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz