Подтвердить что ты не робот

Как наследовать от 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.
4b9b3361

Ответ 1

Вы вызываете конструктор без параметров 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;

часто встречается при реализации конструкторов. Он вызывает унаследованный конструктор с теми же параметрами, которые были переданы потомку.

Ответ 2

Вы можете использовать generics. Он отлично работает без литья типа и утечки памяти (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;