问题 如何从TObjectList 继承而不是从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.

2086
2017-09-15 05:35


起源

重构可以提供帮助。 - smooty86
您的泄密是因为您的代码有缺陷。我们看不到您的代码。无论如何,为什么要进行子类化。您可以使用 TObjectList<TPerson> 直。除非你想为它添加方法。 - David Heffernan
@DavidHeffernan我编辑了添加构造函数和用法的问题。那个代码,用 TObjectList,不泄漏。但如果我改变了 TMembers 声明是 TMembers = class(TObjectList<TPerson>) 现在上面的代码显示内存泄漏。是的,我想为它添加方法。我在这里展示的只是一个简单的组成例子,希望是一个明确的问题。 - alondono
根据你的意见,我重新写了你的问题,让它问我认为你打算问什么。另外,通过这个问题列表,它让我更容易回答!! ;-) - David Heffernan
我的经验是这样一个简单的完整程序使得回答变得更容易,而且它也可以帮助你,因为焦点清晰且畅通无阻。 - David Heffernan


答案:


你正在调用无参数构造函数 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不使用显式参数,而是将与调用封闭方法相同的参数传递给inherited方法。例如:

inherited;

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


13
2017-09-15 06:59



我刚刚尝试了顶级解决方案并实际删除了 True 参数也有效。所以,你只需要 inherited Create;。 - alondono
但最好明确一点。我的版本 TObjectList<T> 强制调用者传递OwnsObjects。 - David Heffernan


你可以使用泛型。没有类型转换和内存泄漏,它工作正常( 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;

1
2017-09-15 06:33



你应该描述一下 为什么 没有memleaks。只是代码无法理解。 - Sir Rufo
要启用内存泄漏报告添加 ReportMemoryLeaksOnShutdown := True; 在项目源文件之前 Application.Initialize。如果存在内存泄漏,则应用程序关闭后将显示报​​告。 - JayDi
这就是你如何证明没有memleaks,但不是 为什么? - Sir Rufo
因为通用类 TObjectList<T> 之后自动销毁该列表中的所有内部对象 free 命令。 - JayDi
那么OP继承了他的班级 TObjectList<T> 他有回忆。这是需要回答的问题。 - Sir Rufo