Ich habe eine Frage bezüglich des Verhaltens der TObjectList-Klasse des Spring4D-Frameworks. In meinem Code erstelle ich eine Liste von geometrischen Figuren wie square
, circle
, triange
, die jeweils als einzelne Klasse definiert sind. Um die geometrischen Figuren automatisch zu befreien, wenn die Liste zerstört wird, habe ich eine Liste vom Typ TObjectList wie folgt definiert:
Wenn ich diesen Code ausführe, wird die Liste geometricFigures
automatisch aus dem Speicher freigegeben, auch wenn ich die Methode Free
nicht auf der Liste anrufe (im auskommentierten Block notieren Sie die auskommentierte Zeile). Ich habe ein anderes Verhalten erwartet. Ich dachte, die Liste benötigt einen expliziten Aufruf von Free (), weil die lokale Variable geometricFigures
keinen Schnittstellentyp verwendet.
Ich bemerkte weiter, dass, wenn die Elemente der Liste nicht in der for-in-Schleife iteriert werden (ich entfernte es vorübergehend aus dem Code), die Liste nicht automatisch freigegeben wird und ich ein Speicherleck erhalte.
Dies führt mich zu der folgenden Frage:
Warum wird die Liste vom Typ TObjectList ( geometricFigures
) automatisch freigegeben, wenn ihre Elemente iteriert werden, aber nicht, wenn die for-in-Schleife aus dem Code entfernt wird?
Ich folgte dem Rat von Sebastian und debuggte den Destruktor. Die Listenelemente werden durch den folgenden Code zerstört:
%Vor%Ich musste meine angenommene Antwort noch einmal überdenken und kam zu dem folgenden Ergebnis:
Rudys Antwort ist meiner Meinung nach richtig, obwohl das beschriebene Verhalten kein Fehler im Framework ist. Ich denke, Rudy macht ein gutes Argument, indem er darauf hinweist, dass ein Rahmen wie erwartet funktionieren sollte. Wenn ich eine For-In-Schleife verwende, erwarte ich, dass es sich um eine schreibgeschützte Operation handelt. Das Löschen der Liste ist nicht das, was ich erwartet habe.
Auf der anderen Seite weisen Fritzw und David Heffernan darauf hin, dass das Design des Spring4D-Frameworks interface-basiert ist und daher auf diese Weise verwendet werden sollte. Solange dieses Verhalten dokumentiert ist (vielleicht könnte Fritzw uns einen Verweis auf die Dokumentation geben) stimme ich David zu, dass meine Verwendung des Frameworks nicht korrekt ist, obwohl ich immer noch denke, dass das Verhalten des Frameworks fehlleitend ist.
Ich bin nicht erfahren genug, um mit Delphi zu entwickeln, um zu bewerten, ob das beschriebene Verhalten tatsächlich ein Fehler ist oder nicht, deshalb habe ich meine angenommene Antwort widerrufen, sorry dafür.
Um warum die Liste zu verstehen, müssen wir verstehen, was hinter den Kulissen vor sich geht.
TObjectList<T>
dient zur Verwendung als Schnittstelle und weist eine Referenzzählung auf. Immer wenn der refcount 0 erreicht, wird die Instanz freigegeben.
Der Refcount für olist
steht jetzt auf 0
Der Enumerator erhöht den refcount von olist
auf 1
Der Enumerator verlässt den Gültigkeitsbereich und der Destruktor des Enumerators wird aufgerufen, wodurch der refcount von olist
auf 0 verringert wird. Dies bedeutet, dass die olist
-Instanz freigegeben wird.
olist
refcount ist 0
Die Zuweisung der olist
Referenz an die Schnittstellenvariable olisti
ruft intern _AddRef
für olist
auf und erhöht den refcount auf 1.
Der Enumerator erhöht den refcount von olist
auf 2
Der Enumerator verlässt den Gültigkeitsbereich und der Destruktor des Enumerators wird aufgerufen, wodurch der refcount von olist
auf 1 verringert wird.
Am Ende der Prozedur wird die Schnittstellenvariable olisti
auf nil
gesetzt, was intern _Release
auf olist
aufruft und den refcount auf 0 reduziert, was bedeutet, dass die olist
Instanz ist befreit.
Wenn Sie den Verweis auf die Schnittstellenvariable olist
zuweisen, ruft% _AddRef
intern auf und erhöht den refcount auf 1.
Der Enumerator erhöht den refcount von olist
auf 2
Der Enumerator verlässt den Gültigkeitsbereich und der Destruktor des Enumerators wird aufgerufen, wodurch der refcount von olist
auf 1 verringert wird.
Am Ende der Prozedur wird die Schnittstellenvariable olist
auf nil
gesetzt, was intern _Release
auf olist
aufruft und den refcount auf 0 reduziert, was bedeutet, dass die olist
Instanz ist befreit.
Um eine Iteration mit for ... do
durchzuführen, muss die Klasse eine GetEnumerator
-Methode haben. Dies gibt offensichtlich selbst (d. H.% Co_de%) als TObjectList<>
Schnittstelle zurück. Nach der Iteration wird die IEnumerator<TGeometricFigure>
freigegeben, ihre Referenzzahl erreicht 0 und die Objektliste wird freigegeben.
Dies ist ein Muster, das Sie oft in C # sehen, aber dort hat es diesen Effekt nicht, da auf die Klasseninstanz immer noch verwiesen wird und der Garbage Collector nicht hineinspringt.
In Delphi ist dies jedoch ein Problem, wie Sie sehen können. Ich denke, die Lösung wäre für die IEnumerator<>
eine separate (möglicherweise verschachtelte) Klasse oder einen Record zu haben, die die Enumeration durchführt und nicht TObjectList<>
(als Self
) zurückgibt. Aber das liegt beim Autor von Spring4D. Sie könnten Stefan Glienke auf dieses Problem aufmerksam machen.
Ihr Anhang zeigt, dass dies nicht genau passiert. Der IEnumerator<>
(oder genauer gesagt, sein Vorgänger TObjectList<>
) gibt einen separaten Enumerator zurück, aber das macht einen (IMO total unnötig, auch wenn die Liste von Anfang an als Schnittstelle verwendet wird) TList<>
/ _AddRef
und Letzteres ist der Schuldige.
Ich sehe mehrere Behauptungen, dass in Spring4D die Klasse nicht als Klasse verwendet werden sollte. Dann sollten solche Klassen nicht im Abschnitt _Release
, sondern stattdessen im Abschnitt interface
der Einheit verfügbar gemacht werden. Wenn solche Klassen verfügbar gemacht werden, sollte der Autor erwarten, dass der Benutzer sie verwendet. Und wenn sie als Klasse verwendbar sind, sollte eine implementation
-Schleife den Container nicht freigeben. Eine davon ist ein Designproblem: entweder die Belichtung als Klasse oder die automatische Freigabe. Es gibt also einen Bug, IMO.
Sie verwenden for in loop
, um durch die Sammlung zu iterieren; Diese Art von Schleife sucht nach einer Methode in der Klasse GetEnumerator
. In Spring4D rufen Sie für TObjectList<T>
das vererbte TList<T>.GetEnumerator
auf, das wie folgt implementiert ist:
Und der Konstruktor für TEnumerator
ist implementiert als:
Beachten Sie, dass es _AddRef
in der Liste aufruft. An diesem Punkt wird Ihre TObjetList
RefCount
zu 1
Da der Aufruf GetEnumerator
eine Schnittstelle zurückgibt, wird beim Beenden der Schleife Freed ausgegeben. Das Destructor
wird wie folgt implementiert:
Beachten Sie, dass es _Release
in der Liste aufruft. Wenn Sie den Debugger verwenden, stellen Sie fest, dass die RefCount
der Liste auf 0 dekrementiert wird und dann _Release
aufgerufen wird, weshalb Ihre Liste freigegeben wird
Wenn Sie die For-in-Schleife in Ihrem ursprünglichen Code entfernen, haben Sie ein Speicherleck:
Ein unerwarteter Speicherverlust ist aufgetreten. Die unerwarteten kleinen Blocklecks sind:
1 - 12 Byte: TGeometricFigure x 6, TMoveArrayManager x 1, Unbekannt x 1
21 - 28 Bytes: TList x 1
29 - 36 Bytes: TCriticalSection x 1
53 - 60 Byte: TCollectionChangedEventImpl x 1, Unbekannt x 1
77 - 84 Byte: TObjectList x 1
Edit: Soeben hat Rudy Velthuis Antwort gesehen. Dies ist kein Spring4D-Fehler. Sie sollten nicht die klassenbasierten Frameworks-Frameworks verwenden. Sie müssen die interface-basierten Sammlungen verwenden. Auch nicht mit Spring4D verwandt, aber in Delphi wird empfohlen, Schnittstellenreferenzen nicht mit Objektreferenzen zu mischen
Die Collection-Klassen von Spring4D sind für die Verwendung mit Schnittstellen konzipiert, TObjectList implementiert IList. Wenn Sie also über die Schnittstelle darauf verweisen, funktioniert es wie erwartet.
%Vor%Erstellen Sie Ihre eigene TGemotricFigures-Liste, die den Destruktor überschreibt. Dann können Sie ziemlich schnell sagen, wer den Destruktor aufruft.
%Vor%Meine Vermutung ist, dass etwas in geometricFigure.ToString () etwas tut, was nicht passieren sollte, dass als Nebeneffekt geometricFigues zerstört. Verwenden Sie den FastMM4 FullDebugMode und es ist wahrscheinlich, dass Sie weitere Informationen erhalten.