Delphi 中的通用 TObjectlist 不能很好地使用智能指针

Smartpointers do not work well with a generic TObjectlist in Delphi

我正在使用 Spring4D 在 Delphi 10.3 Rio 中测试智能指针。这是我的测试程序。我创建了一个通用 TObjectList,我想使用 Shared.Make(TTestObj.Create) 添加简单的 TObject 到这个列表。问题是每当我将一个对象添加到列表中时,前一个对象就会被释放。查看我的程序的输出。有谁知道如何解决这个问题?

program TestSmartPointer;

{$APPTYPE CONSOLE}

uses
  Spring,
  Diagnostics,
  Classes,
  SysUtils,
  System.Generics.Collections;

type
  TTestObj = class
  private
    FDescription: string;
  public
    property Description: string read FDescription write FDescription;
    destructor Destroy; override;
  end;
  TTestList = class(TObjectList<TTestObj>)
    destructor Destroy; override;
  end;

var
  LISTITEMCOUNT: integer;
  LISTCOUNT: integer;

procedure Test_SmartPointer;
begin
  Writeln('SmartPointer test started');
  var lTestList := Shared.Make(TTestList.Create)();
  lTestList.OwnsObjects := false;
  for var i := 1 to 10 do
  begin
    var lTestObj := Shared.Make(TTestObj.Create)();
//    var lTestObj := TTestObj.Create;
    lTestObj.Description := i.ToString;
    Writeln('TestObj added to Testlist with description ' + lTestObj.Description);
    lTestList.Add(lTestObj);
  end;
  Writeln('SmartPointer test finished');
end;

{ TTestObj }

destructor TTestObj.Destroy;
begin
  Writeln(format('TTestObj with description %s is destroyed', [FDescription]));
  inherited;
end;

{ TTestList }

destructor TTestList.Destroy;
begin
  Writeln('TTestList is destroyed');
  inherited;
end;

begin
  Test_SmartPointer;
  Readln;
end.

问题是您的 TObjectList 持有原始 TTestObj 对象指针,而不是 Shared.Make<T>() return 的 IShared<TTestObj> 接口。

var lTestList := Shared.Make(TTestList.Create)(); 中,您正在创建一个 IShared<TTestList>(一个 reference to function: TTestList)来包装您正在创建的 TTestList 对象。您正在 IShared 上调用 (),这会调用 return 原始 TTestList 对象指针的函数。在这个例子中这是可以的,因为 IShared 将在 Test_SmartPointer() 的生命周期内保存在一个隐藏变量中,因此它的引用计数为 1,使 TTestList 保持活动状态。

var lTestObj := Shared.Make(TTestObj.Create)(); 中,您正在做同样的事情,这次是 IShared<TTestObj> returning 一个 TTestObj 对象指针。但是,当 lTestObj 在每次循环迭代结束时超出范围时, IShared 的引用计数将减少。由于没有对该接口的进一步引用,它的引用计数降为 0,破坏了 IShared 后面的对象,这反过来又破坏了其关联的 TTestObj 对象,使 TObjectList 悬空TTestObj 指针(但你不会遇到任何崩溃,因为你没有以任何方式访问存储的 TTestObj 对象,甚至在 TObjectList 析构函数中也没有,因为 OwnsObjects=false).

您需要更改 TTestList 以容纳 IShared<TTestObj> 个元素而不是 TTestObj 个元素(在这种情况下,您应该使用 TList<T> 而不是 TObjectList<T> ),并在调用 Shared.Make():

时摆脱 IShared 接口上的 () 调用
program TestSmartPointer;

{$APPTYPE CONSOLE}

uses
  Spring,
  Diagnostics,
  Classes,
  SysUtils,
  System.Generics.Collections;

type
  TTestObj = class
  private
    FDescription: string;
  public
    property Description: string read FDescription write FDescription;
    destructor Destroy; override;
  end;

  TTestList = class(TObjectList<IShared<TTestObj>>)
    destructor Destroy; override;
  end;

var
  LISTITEMCOUNT: integer;
  LISTCOUNT: integer;

procedure Test_SmartPointer;
begin
  Writeln('SmartPointer test started');
  var lTestList := Shared.Make(TTestList.Create);
  for var i := 1 to 10 do
  begin
    var lTestObj := Shared.Make(TTestObj.Create);
    lTestObj.Description := i.ToString;
    Writeln('TestObj added to Testlist with description ' + lTestObj.Description);
    lTestList.Add(lTestObj);
  end;
  Writeln('SmartPointer test finished');
end;

{ TTestObj }

destructor TTestObj.Destroy;
begin
  Writeln(Format('TTestObj with description %s is destroyed', [FDescription]));
  inherited;
end;

{ TTestList }

destructor TTestList.Destroy;
begin
  Writeln('TTestList is destroyed');
  inherited;
end;

begin
  Test_SmartPointer;
  Readln;
end.

这是有效的代码(感谢 Remy Lebeau)。由于 Delphi 没有垃圾收集器并且 ARC 已被删除,我一直在寻找一个通用结构来自动释放对象。我对 smartpointers 的印象是它有点太复杂了,不能用作自动释放对象的易于使用的通用结构。

program TestSmartPointer;

{$APPTYPE CONSOLE}

uses
  Spring,
  Diagnostics,
  Classes,
  SysUtils,
  System.Generics.Collections;

type
  TTestObj = class
  private
    FDescription: string;
  public
    property Description: string read FDescription write FDescription;
    destructor Destroy; override;
  end;
  TTestList = class(TList<IShared<TTestObj>>)
  public
    destructor Destroy; override;
  end;

procedure Test_SmartPointer;
var
  lTestList: IShared<TTestList>;
  lTestObj: IShared<TTestObj>;
  i: integer;
begin
  Writeln('SmartPointer test started');
  lTestList := Shared.Make(TTestList.Create);
  for i := 1 to 10 do
  begin
    lTestObj := Shared.Make(TTestObj.Create);
    lTestObj.Description := i.ToString;
    Writeln(format('TestObj with description %s added to Testlist', [lTestObj.Description]));
    lTestList.Add(lTestObj);
  end;
  for lTestObj in lTestList do
  begin
    writeln(lTestObj.Description);
  end;

  Writeln('SmartPointer test finished');
end;

{ TTestObj }

destructor TTestObj.Destroy;
begin
  Writeln(format('TestObj with description %s is destroyed', [FDescription]));
  inherited;
end;

{ TTestList }

destructor TTestList.Destroy;
begin
  Writeln('TTestList is destroyed');
  inherited;
end;

begin
  Test_SmartPointer;
  Readln;
end.

Output