如何从TObjectList <t>继承而不是从TObjectList继承

时间:2015-09-15 05:35:42

标签: delphi inheritance generic-collections

为什么这个程序会报告内存泄漏?

{$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 个答案:

答案 0 :(得分:14)

您正在调用TObjectList<T>的无参数构造函数。这实际上是TList<T>的构造函数,TObjectList<T>的派生类。

TObjectList<T>中声明的所有构造函数都接受一个名为AOwnsObjects的参数,该参数用于初始化OwnsObjects属性。因为您绕过了该构造函数,OwnsObjects默认为False,并且列表的成员不会被销毁。

您应确保调用初始化TObjectList<T>的{​​{1}}构造函数。例如:

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的单个参数构造函数。

documentation有以下信息:

  

继承的保留字在实现多态行为中起着特殊的作用。它可以在方法定义中出现,有或没有标识符。

     

如果继承后跟一个成员的名称,它表示一个普通的方法调用或对属性或字段的引用,除了搜索引用的成员以封闭方法的类的直接祖先开始。例如,在:

TObjectList<T>
     

发生在方法的定义中,它调用继承的Create。

     

当继承后面没有标识符时,它引用与封闭方法同名的继承方法,或者如果封闭方法是消息处理程序,则引用同一消息的继承消息处理程序。在这种情况下,inherited不使用显式参数,而是将与调用封闭方法相同的参数传递给inherited方法。例如:

inherited Create(...);
     

在构造函数的实现中经常发生。它使用与传递给后代的相同参数调用继承的构造函数。

答案 1 :(得分:1)

您可以使用泛型。没有类型转换和内存泄漏(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;