Как наследовать от 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;
Другие вопросы по тегам