Почему список типов tobjectlist освобождается автоматически после итерации?
у меня есть вопрос относительно поведения класса TObjectList фреймворка Spring4D. В моем коде я создаю список геометрических фигур, таких как square
, circle
, triange
, каждый определяется как отдельный класс. Чтобы автоматически освободить геометрические фигуры при уничтожении списка, я определил список типа TObjectList следующим образом:
procedure TForm1.FormCreate(Sender: TObject);
var
geometricFigures: TObjectList<TGeometricFigure>;
geometricFigure: TGeometricFigure;
begin
ReportMemoryLeaksOnShutdown := true;
geometricFigures := TObjectList<TGeometricFigure>.Create();
try
geometricFigures.Add(TCircle.Create(4,2));
geometricFigures.Add(TCircle.Create(0,4));
geometricFigures.Add(TRectangle.Create(3,10,4));
geometricFigures.Add(TSquare.Create(1,5));
geometricFigures.Add(TTriangle.Create(5,7,4));
geometricFigures.Add(TTriangle.Create(2,6,3));
for geometricFigure in geometricFigures do begin
geometricFigure.ToString();
end;
finally
//geometricFigures.Free(); -> this line is not required (?)
end;
end;
если я запускаю этот код в список geometricFigures
автоматически освобождается из памяти, даже если я не вызов метода Free
on список (Примечание закомментированная строка в блоке finally). Я ожидал другого поведения, я думал, что список нуждается в явном вызове Free (), потому что локальная переменная geometricFigures
не использует тип интерфейса.
Я также заметил, что если элементы списка не повторяются в цикле for-in (я временно удалил его из кода), список не освобождается автоматически, и я получаю утечку памяти.
это приводит меня к следующему вопросу:
Почему список TObjectList тип (geometricFigures
) освобождается автоматически, когда его элементы повторяются, но не если цикл for-in удаляется из кода?
обновление
{$REGION 'TList<T>.TEnumerator'}
constructor TList<T>.TEnumerator.Create(const list: TList<T>);
begin
inherited Create;
fList := list;
fList._AddRef;
fVersion := fList.fVersion;
end;
destructor TList<T>.TEnumerator.Destroy;
begin
fList._Release;
inherited Destroy; // items get destroyed here
end;
обновление
мне пришлось пересмотреть свой принятый ответ и прийти к следующему выводу:
на мой взгляд, ответ Руди правильный, хотя описанное поведение не может быть ошибкой в рамках. Я думаю, что Руди делает хороший аргумент, указывая, что структура должна работать так, как ожидалось. Когда я использую цикл for-in, я ожидаю, что это будет операция только для чтения. Очистка списка после этого-это не то, что я ожидал.
С другой стороны, Fritzw и Дэвид Хеффернан указывают, что дизайн spring4d framework основан на интерфейсе и поэтому должен использоваться таким образом. Пока такое поведение документированный (возможно, Fritzw мог бы дать нам ссылку на документацию) я согласен с Дэвидом, что мое использование фреймворка неверно, хотя я все еще думаю, что поведение фреймворка является пропуском.
Я недостаточно опытен в разработке с Delphi, чтобы оценить, действительно ли описанное поведение является ошибкой или Поэтому не отозвало мой принятый ответ, извините за это.
5 ответов
понять почему список освободили нам нужно понять, что происходит за кулисами.
TObjectList<T>
предназначено для использования в качестве интерфейса и подсчет ссылок. Всякий раз, когда счетчик достигает 0 экземпляр будет освобожден.
procedure foo;
var
olist: TObjectList<TFoo>;
o: TFoo;
begin
olist := TObjectList<TFoo>.Create();
счетчик для olist
теперь в 0
try
olist.Add( TFoo.Create() );
olist.Add( TFoo.Create() );
for o in olist do
перечислитель увеличивает refcount olist
к 1
begin
o.ToString();
end;
перечислитель выходит за рамки и вызывается деструктор перечислителя, который будет уменьшать refcount olist
до 0, и это означает, что olist
экземпляр освобождается.
finally
//olist.Free(); -> this line is not required (?)
end;
end;
в чем разница при использовании переменной интерфейса?
procedure foo;
var
olist: TObjectList<TFoo>;
olisti: IList<TFoo>;
o: TFoo;
begin
olist := TObjectList<TFoo>.Create();
olist
refcount равно 0
olisti := olist;
назначение olist
ссылка на переменную интерфейс olisti
внутренне называю _AddRef
on olist
и увеличьте refcount до 1.
try
olist.Add( TFoo.Create() );
olist.Add( TFoo.Create() );
for o in olist do
в перечислитель увеличить refcount olist
в 2
begin
o.ToString();
end;
перечислитель выходит из области видимости, и вызывается деструктор перечислителя, который уменьшит refcount olist
для 1.
finally
//olist.Free(); -> this line is not required (?)
end;
end;
в конце процедуры переменная интерфейса olisti
будет установлен в nil
, который будет внутренне называть _Release
on olist
и уменьшите refcount до 0, и это означает, что olist
экземпляр освобождается.
то же происходит, когда мы назначаем ссылку непосредственно из конструктора переменной интерфейса:
procedure foo;
var
olist: IList<TFoo>;
o: TFoo;
begin
olist := TObjectList<TFoo>.Create();
назначение ссылки на переменную интерфейса olist
внутренне называю _AddRef
и увеличьте refcount до 1.
olist.Add( TFoo.Create() );
olist.Add( TFoo.Create() );
for o in olist do
перечислитель увеличивает refcount olist
в 2
begin
o.ToString();
end;
перечислитель выходит из области видимости, и вызывается деструктор перечислителя, который уменьшит refcount olist
для 1.
end;
в конце процедуры переменная интерфейса olist
будет установлен в nil
, который будет внутренне называть _Release
on olist
и уменьшите refcount до 0, и это означает, что olist
экземпляр освобождается.
для выполнения итерации с for ... do
, класс должен иметь GetEnumerator
метод. Это, очевидно, возвращает себя (т. е. TObjectList<>
) в качестве IEnumerator<TGeometricFigure>
интерфейс. После итерации IEnumerator<>
освобождается, его счетчик ссылок достигает 0, и objectlist освобождается.
это шаблон, который вы часто видите, скажем, в C#, но там он не имеет этого эффекта, потому что экземпляр класса все еще ссылается, и сборщик мусора не будет прыгать.
в Delphi, однако, это проблема, как вы можете видеть. Я думаю, решение было бы для TObjectList<>
иметь отдельный (возможно, вложенный) класс или запись, которая выполняет перечисление, и не возвращать Self
(as IEnumerator<>
). Но это зависит от автора Spring4D. Вы могли бы довести эту проблему до сведения Стефана Гленке.
обновление
ваши дополнения показывает, что это не совсем то, что происходит. The TObjectList<>
(точнее, его предок!--9-->) возвращает отдельный перечислитель, но это делает (IMO совершенно ненужным, даже если список используется в качестве интерфейса с самого начала)_AddRef
/_Release
и последний является виновником.
Примечание
я вижу несколько утверждений, что в Spring4D класс не должен использоваться как класс. Тогда такие классы не должны выставляться в , но в implementation
секция блока вместо этого. Если такие классы открыты, автор должен ожидать пользователя использовать их. И если они могут использоваться как класс, то a for-in
loop не должен освобождать контейнер. Одна из них-проблема дизайна: либо экспозиция как класс, либо автоматическое освобождение. Итак, есть ошибка, ИМО.
вы используете for in loop
выполнить итерации через коллекцию, что цикл ищет метод в классе под названием GetEnumerator
. В Spring4D, для TObjectList<T>
, вы в конечном итоге вызывая унаследовал TList<T>.GetEnumerator
, который реализуется как:
function TList<T>.GetEnumerator: IEnumerator<T>;
begin
Result := TEnumerator.Create(Self);
end;
и конструктор для TEnumerator
реализуется как:
constructor TList<T>.TEnumerator.Create(const list: TList<T>);
begin
inherited Create;
fList := list;
fList._AddRef;
fVersion := fList.fVersion;
end;
обратите внимание, что он будет называть _AddRef
в списке. В этот момент, ваш TObjetList
RefCount
идет в 1
С GetEnumerator
вызов возвращает интерфейс, когда вы закончите цикл, он освободится. The Destructor
реализован следующим образом:
destructor TList<T>.TEnumerator.Destroy;
begin
fList._Release;
inherited Destroy;
end;
обратите внимание, что он называет _Release
в списке. Если вы используете отладчик, вы заметите, что он уменьшает RefCount
из списка до 0, а затем он вызывает _Release
, так вот почему ваш список освободившись
если вы удалите цикл for in в исходном коде, вы получите утечку памяти:
Неожиданный Памяти Утечка
произошла неожиданная утечка памяти. Неожиданные небольшие утечки блока:
1 - 12 байт: TGeometricFigure x 6, TMoveArrayManager x 1, неизвестный x 1
21 - 28 байт: TList x 1
29 - 36 байт: TCriticalSection x 1
53 - 60 байт: TCollectionChangedEventImpl x 1, неизвестно x 1
77 - 84 байт: TObjectList x 1
редактировать: только что видел Руди Velthuis ответа. Это не Spring4D жук. Вы должны не используйте коллекции на основе классов Framework. Вы должны использовать интерфейс коллекций. Кроме того, не связано с Spring4D, но в Delphi, вам рекомендуется не смешивать ссылки на интерфейс с ссылками на объекты
классы коллекции Spring4D предназначены для использования с интерфейсами, tobjectlist реализует IList, поэтому, если вы ссылаетесь на него с помощью интерфейса, он будет работать так, как ожидалось.
procedure TForm1.FormCreate(Sender: TObject);
var
geometricFigures: IList<TGeometricFigure>;
geometricFigure: TGeometricFigure;
begin
ReportMemoryLeaksOnShutdown := true;
geometricFigures := TCollections.CreateObjectList<TGeometricFigure>(true);
geometricFigures.Add(TCircle.Create(4,2));
geometricFigures.Add(TCircle.Create(0,4));
geometricFigures.Add(TRectangle.Create(3,10,4));
geometricFigures.Add(TSquare.Create(1,5));
geometricFigures.Add(TTriangle.Create(5,7,4));
geometricFigures.Add(TTriangle.Create(2,6,3));
for geometricFigure in geometricFigures do
begin
geometricFigure.ToString();
end;
end;
создайте свой собственный список TGemoetricFigures, который переопределяет деструктор. Тогда вы можете довольно быстро сказать, кто вызывает деструктор.
type
TGeometricFigures = class(TObjectList<TGeometricFigure>)
public
destructor Destroy; override;
end;
implementation
{ TGeometricFigures }
destructor TGeometricFigures.Destroy;
begin
ShowMessage('TGeometricFigures.Destroy was called');
inherited;
end;
procedure FormCreate(Sender: TObject);
var
geometricFigures: TGeometricFigures;
geometricFigure: TGeometricFigure;
begin
ReportMemoryLeaksOnShutdown := true;
geometricFigures := TGeometricFigures.Create;
try
geometricFigures.Add(TCircle.Create(4,2));
geometricFigures.Add(TCircle.Create(0,4));
geometricFigures.Add(TRectangle.Create(3,10,4));
geometricFigures.Add(TSquare.Create(1,5));
geometricFigures.Add(TTriangle.Create(5,7,4));
geometricFigures.Add(TTriangle.Create(2,6,3));
for geometricFigure in geometricFigures do begin
geometricFigure.ToString();
end;
finally
//geometricFigures.Free(); -> this line is not required (?)
end;
end;
Я предполагаю, что что-то внутри geometricFigure.ToString () делает что-то, что не должно происходить, что в качестве побочного эффекта разрушает geometricFigues. Используйте Fastmm4 FullDebugMode, и, вероятно, вы получите больше информации.