Как наследовать от TObjectList<T> вместо наследования от TObjectList
Почему эта программа сообщает об утечках памяти?
{$APPTYPE CONSOLE}
uses
System.Generics.Collections;
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create;
end;
constructor TDerivedGenericObjectList.Create;
begin
inherited;
end;
var
List: TDerivedGenericObjectList;
begin
ReportMemoryLeaksOnShutdown := True;
List := TDerivedGenericObjectList.Create;
List.Add(TObject.Create);
List.Free;
end.
2 ответа
Вы вызываете конструктор без параметров TObjectList<T>
, Это на самом деле конструктор TList<T>
, класс из которого TObjectList<T>
является производным
Все конструкторы объявлены в TObjectList<T>
принять параметр с именем AOwnsObjects
который используется для инициализации OwnsObjects
имущество. Потому что вы обходите этот конструктор, OwnsObjects
по умолчанию False
и члены списка не уничтожаются.
Вы должны убедиться, что вы вызываете конструктор TObjectList<T>
эта инициализация OwnsObjects
, Например:
{$APPTYPE CONSOLE}
uses
System.Generics.Collections;
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create;
end;
constructor TDerivedGenericObjectList.Create;
begin
inherited Create(True);
end;
var
List: TDerivedGenericObjectList;
begin
ReportMemoryLeaksOnShutdown := True;
List := TDerivedGenericObjectList.Create;
List.Add(TObject.Create);
List.Free;
end.
Возможно, лучшим вариантом было бы сделать так, чтобы ваш конструктор также предлагал AOwnsObjects
параметр:
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create(AOwnsObjects: Boolean = True);
end;
constructor TDerivedGenericObjectList.Create(AOwnsObjects: Boolean);
begin
inherited Create(AOwnsObjects);
end;
Или же:
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create(AOwnsObjects: Boolean = True);
end;
constructor TDerivedGenericObjectList.Create(AOwnsObjects: Boolean);
begin
inherited;
end;
Итак, вы можете спросить, почему оригинальная версия выбрала TList<T>
конструктор, а не один в TObjectList<T>
, Что ж, давайте посмотрим на это более подробно. Вот ваш код снова:
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create;
end;
constructor TDerivedGenericObjectList.Create;
begin
inherited;
end;
когда inherited
используется таким образом, компилятор ищет конструктор с точно такой же сигнатурой, что и эта. Он не может найти его в TObjectList<T>
потому что все они имеют параметр. Это можно найти в TList<T>
и вот тот, который он использует.
Как вы упоминаете в комментариях, следующий вариант не протекает:
constructor TDerivedGenericObjectList.Create;
begin
inherited Create;
end;
Этот синтаксис, в отличие от голого inherited
, найдет методы, которые соответствуют при замене параметров по умолчанию. И поэтому один параметр конструктора TObjectList<T>
называется.
В документации есть эта информация:
Зарезервированное слово "наследуется" играет особую роль в реализации полиморфного поведения. Это может происходить в определениях методов с идентификатором или без него после него.
Если за наследованным следует имя члена, он представляет обычный вызов метода или ссылку на свойство или поле, за исключением того, что поиск ссылочного члена начинается с непосредственного предка класса включающего метода. Например, когда:
inherited Create(...);
происходит в определении метода, он вызывает унаследованный Create.
Когда унаследованный не имеет идентификатора после него, он ссылается на унаследованный метод с тем же именем, что и метод вложения, или, если метод вложения является обработчиком сообщений, на унаследованный обработчик сообщений для того же сообщения. В этом случае унаследованный не принимает явных параметров, но передает унаследованному методу те же параметры, с которыми был вызван включающий метод. Например:
inherited;
часто встречается при реализации конструкторов. Он вызывает унаследованный конструктор с теми же параметрами, которые были переданы потомку.
Вы можете использовать дженерики. Работает нормально без приведения типов и утечки памяти (TObjectList<T>
или же TObjectDictionary<T>
списки уничтожают внутренние объекты автоматически по свободной команде).
Несколько советов:
TObjectList<TPerson>
- уничтожить список людей автоматически на бесплатной лайкеmembersList.Free
;TList<TPerson>
- не уничтожать список лиц. Вы должны создать деструктор и освободить вручную каждого человека в списке;
Вот пример вашего кода (с использованием нового конструктора, без утечки памяти и с обратной совместимостью со старым кодом - см. GetPerson
):
type
TPerson = class
public
Name: string;
Age: Integer;
function Copy: TPerson;
end;
TMembers = class(TObjectList<TPerson>)
private
function GetPerson(i: Integer): TPerson;
public
property Person[i: Integer]: TPerson read GetPerson;
constructor Create(SourceList: TMembers); overload;
end;
{ TPerson }
function TPerson.Copy: TPerson;
var
person: TPerson;
begin
person := TPerson.Create;
person.Name := Self.Name;
person.Age := Self.Age;
Result := person;
end;
{ TMembers }
constructor TMembers.Create(SourceList: TMembers);
var
person: TPerson;
begin
inherited Create;
for person in SourceList do
begin
Self.Add(person.Copy);
end;
end;
function TMembers.GetPerson(i: Integer): TPerson;
begin
Result := Self[i];
end;
procedure TForm21.Button1Click(Sender: TObject);
var
person: TPerson;
memsList1: TMembers;
memsList2: TMembers;
begin
// test code
memsList1 := TMembers.Create;
person := TPerson.Create;
person.Name := 'name 1';
person.Age := 25;
memsList1.Add(person);
person := TPerson.Create;
person.Name := 'name 2';
person.Age := 27;
memsList1.Add(person);
memsList2 := TMembers.Create(memsList1);
ShowMessageFmt('mems 1 count = %d; mems 2 count = %d', [memsList1.Count, memsList2.Count]);
FreeAndNil(memsList1);
FreeAndNil(memsList2);
end;