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



步骤1:用以下代码编写应用程序:

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:重复步骤有时应用程序会返回内存不足的

问题就在这里:

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。

最新更新