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
我正在使用 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