使用带有泛型的对象列表的内存溢出

memory overflow using the object list with generics

第一步: 使用代码编写应用程序:

unit Unit1;

interface

uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Generics.Collections,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls;

type
TObjChild = class;

TObjTest = class
private

    FName: string;  
    FChilds: TList<TObjChild>;

public
    property Name: string read FName write FName;
    property Childs: TList<TObjChild> read FChilds write FChilds;

    constructor Create;
    destructor Destroy; override;
end;

TObjChild = class
private

    FAdress: string;  
    FPostalCode: string;

public

    property Adress: string read FAdress write FAdress;
    property PostalCode: string read FPostalCode write FPostalCode;

end;

TForm1 = class(TForm)

    Button1: TButton;
    procedure Button1Click(Sender: TObject);

private
{ Private declarations }

public
{ Public declarations }

end;

var
Form1: TForm1;

implementation

{$R *.fmx}
{ TObjTeste }

constructor TObjTest.Create;
begin

    FChilds := TObjectList<TObjChild>.Create;

end;

destructor TObjTest.Destroy;
var

    i: integer;

begin

    for i := 0 to FChilds.count -1 do
    begin
        FChilds[I].Free;
    end;

    FreeAndNil(FChilds);
    inherited;

end;

procedure TForm1.Button1Click(Sender: TObject);
var

    I: Integer;
    ListObjs: TList<TObjTest>;
    lObjTeste: TObjTest;
    lObjChild: TObjChild;
    J: Integer;

begin

    ListObjs := TList<TObjTest>.Create;

    for I := 0 to 5000 do
    begin
        lObjTeste := TObjTest.Create;

        for J := 0 to 2000 do
        begin
            lObjChild := TObjChild.Create;
            lObjTeste.FChilds.Add(lObjChild)
        end;

        ListObjs.Add(lObjTeste);

    end;

    if MessageDlg('Delete objects?', TMsgDlgType.mtConfirmation, [TMsgDlgBtn.mbOK], 0) = idOK then
    begin
        for I := 0 To ListObjs.Count - 1
        begin
            ListObjs[I].Free;
        end;

        FreeAndNil(ListObjs);
    end;

end;

end.

第 2 步:运行 应用程序并按下按钮 1

按下OK按钮后应用程序的messagedlg没有释放内存

第 3 步:有时应用程序重复步骤 returns 内存不足

问题出在这里:

constructor TObjTest.Create;
begin
  FChilds := TObjectList<TObjChild>.Create;
end;

destructor TObjTest.Destroy;
var
  i: integer;
begin
  for i := 0 to FChilds.count - 1 do
  begin
    FChilds[i].Free;
  end;
  FreeAndNil(FChilds);
  inherited;
end;

默认情况下 TObjectList<T> 拥有其成员的所有权。所以你不需要,也不应该,在析构函数中释放成员。

所以在这里:

for i := 0 to FChilds.count - 1 do
begin
  FChilds[i].Free;
end;

你释放了成员。但是然后在这里:

FreeAndNil(FChilds);

对象列表也释放成员。谁已经被释放了。双重释放会导致您的运行时错误。

修复是删除对象列表成员的显式释放并依赖列表来完成工作:

destructor TObjTest.Destroy;
begin
  FChilds.Free;
  inherited;
end;

其成员的所有权是 TObjectList<T> 存在的唯一原因。这是除 TList<T> 提供的功能之外它提供的唯一功能。在这里阅读:http://docwiki.embarcadero.com/Libraries/en/System.Generics.Collections.TObjectList

最后,child的复数是children。