Warum wird eine Liste vom Typ TObjectList nach der Iteration automatisch freigegeben?

7

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:

%Vor%

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?

Aktualisieren

Ich folgte dem Rat von Sebastian und debuggte den Destruktor. Die Listenelemente werden durch den folgenden Code zerstört:

%Vor%

Aktualisieren

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.

    
MUG4N 02.07.2016, 17:00
quelle

5 Antworten

4

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.

%Vor%

Der Refcount für olist steht jetzt auf 0

%Vor%

Der Enumerator erhöht den refcount von olist auf 1

%Vor%

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.

%Vor%

Was ist der Unterschied bei der Verwendung einer Schnittstellenvariablen?

%Vor%

olist refcount ist 0

%Vor%

Die Zuweisung der olist Referenz an die Schnittstellenvariable olisti ruft intern _AddRef für olist auf und erhöht den refcount auf 1.

%Vor%

Der Enumerator erhöht den refcount von olist auf 2

%Vor%

Der Enumerator verlässt den Gültigkeitsbereich und der Destruktor des Enumerators wird aufgerufen, wodurch der refcount von olist auf 1 verringert wird.

%Vor%

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.

Das Gleiche passiert, wenn wir die Referenz direkt aus dem Konstruktor der Schnittstellenvariablen zuweisen:

%Vor%

Wenn Sie den Verweis auf die Schnittstellenvariable olist zuweisen, ruft% _AddRef intern auf und erhöht den refcount auf 1.

%Vor%

Der Enumerator erhöht den refcount von olist auf 2

%Vor%

Der Enumerator verlässt den Gültigkeitsbereich und der Destruktor des Enumerators wird aufgerufen, wodurch der refcount von olist auf 1 verringert wird.

%Vor%

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.

    
Sir Rufo 04.07.2016, 09:20
quelle
8

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.

Aktualisieren

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.

Hinweis

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.

    
Rudy Velthuis 02.07.2016 18:27
quelle
3

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:

%Vor%

Und der Konstruktor für TEnumerator ist implementiert als:

%Vor%

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:

%Vor%

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:

Unerwarteter Speicherverlust

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

    
Agustin Ortu 02.07.2016 18:46
quelle
3

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%     
Vincent Parrett 03.07.2016 22:30
quelle
1

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.

    
Sebastian Z 02.07.2016 18:18
quelle

Tags und Links