Delphi 如何释放嵌套(对象类型)字段类?
我需要实现一个析构函数,它能够从 相同的基类,并在其上提供一个免费版本,以避免所有这些内存泄漏 为什么??因为FMyFirstClass是否可以创建,这取决于我的应用程序的流程,我无法保证何时创建它来释放它,我也不想用NIL-check类型的代码填充所有析构函数,因为我有很多这样的字段 我正在尝试使用新的RTTI来获取基于TBaseClass的所有字段,但是我无法获取对象字段的实例,我也没有主意了Delphi 如何释放嵌套(对象类型)字段类?,delphi,oop,rtti,Delphi,Oop,Rtti,我需要实现一个析构函数,它能够从 相同的基类,并在其上提供一个免费版本,以避免所有这些内存泄漏 为什么??因为FMyFirstClass是否可以创建,这取决于我的应用程序的流程,我无法保证何时创建它来释放它,我也不想用NIL-check类型的代码填充所有析构函数,因为我有很多这样的字段 我正在尝试使用新的RTTI来获取基于TBaseClass的所有字段,但是我无法获取对象字段的实例,我也没有主意了 我走对了吗?您的建议是什么?调用Free对nil实例没有影响。它是故意这样设计的。你的析构函数应该
我走对了吗?您的建议是什么?调用
Free
对nil实例没有影响。它是故意这样设计的。你的析构函数应该在逻辑上拥有的对象类型的任何字段上调用Free
,这与对象是否被构造无关。我认为你的方法不对。只需释放您声明/创建的类中的对象。正如Barry所说,您不需要进行零检查。Delphi对象应始终由其他对象“拥有”,并且当调用其析构函数时,所有者对象负责释放拥有的对象。因为这是为每一个类做的,所以不应该有任何问题。您可以使用RTTI来实现这一点,但这将是一个时间和资源的腰部,因为编写析构函数非常简单
例如,基类引入了两个ToObject类型字段:
TBaseClass = class
public
destructor Destroy; override;
end;
TFirstClass = class(TBaseClass)
FMyProp: string;
end;
TSecondClass = class(TBaseClass)
FMyFirstClass: TFirstClass;
end;
引入新字段的子类应该重写析构函数并释放这些对象,然后调用inherited
,让父类有机会释放它引入的对象。例如:
TBaseClass = class
private
public
OwnedObject: TObject;
NotOwnedObject: TObject;
destructor Destroy;override;
end;
destructor TBaseClass.Destroy;override;
begin
OwnedObject.Free; // TBaseClass owns this object, it should be destroyed when
// TBaseClass is destroyed.
// Do NOT call NotOwnedObject.Free: that object is not owned by TBaseClass,
// a different object is responsible for calling Free on it.
// ALWAYS call "inherited" from your destructor, it allows daisy-chaining destructors.
// more on this in the next example.
inherited;
end;
在你的问题中,你说:
我需要实现一个析构函数,它能够从同一个基类中找到所有(对象类型)字段,并在其上提供一个空闲空间,以避免所有这些内存泄漏
如您所见,这不是实现的方法:派生类的析构函数调用继承的
,因此基类有机会释放它引入的字段
- 正如巴里·凯利所说,在调用
之前,您不需要检查.Free
,因为Free本身就是这样做的nil
- 正如David Heffernan所说,你所需要做的就是在任何地方都遵循这种模式,你会没事的,没有内存泄漏。这就是VCL的工作原理李>
- 正如mjn所说,请注意不要在不释放旧对象的情况下使用其他对象覆盖所拥有的对象:这样做会丢失对旧对象的最后一个引用,并且无法再释放它,这将导致内存泄漏
- 您在其他答案中收到的建议是正确的。确保将每个对象实例化与析构函数中相应的
Free
配对
我想指出为什么不能使用RTTI查找所有对象实例并释放它们。某些对象实例可能不属于正在销毁的对象。如果您有一个字段,其中包含对系统中其他实体拥有的对象的引用,则您无权销毁它。RTTI无法告诉您谁拥有该对象,只有您可以知道。您使用了错误的方式。你必须保存引用才能知道你要释放什么,如果你不知道流程,那么你的设计就是错误的 如果您的需求不寻常,可以重构基类以保存所有引用,请检查此代码是否可以帮助您:
TFirstClass = class(TBaseClass)
public
AnOtherOwnedObject: TObject;
AnOtherNotOwnedObject: TObject;
destructor Destroy;override;
end;
destructor TFirstClass.Destroy;override;
begin
// Free the stuff we owned and *we* introduced:
AnOtherOwnedObject.Free;
// Call the inherited destructor so the base class can free fields they introduced:
inherited;
end;
程序无基础;
{$APPTYPE控制台}
使用
类,sysutil;
类型
TBase=类别(TObject)
严格保密
类var实例:TList;
类:布尔型;
类程序清除(实例:TBase);
私有的
类程序调试;
受保护的
类构造函数创建;
类析构函数销毁;
公众的
构造函数创建;
毁灭者毁灭;推翻
结束;
TItem=类别(TBase)
结束;
{TBase}
类过程TBase.Clear(实例:TBase);
变量
I:整数;
项目:TBase;
开始
对于I:=0到TBase.Instances.Count-1 do
开始
Item:=TBase.Instances[I];
如果是项目实例,则
项目.销毁;
结束;
TBase.Instances.Clear;
结束;
类构造函数TBase.Create;
开始
TBase.Instances:=TList.Create;
结束;
构造函数TBase.Create;
开始
如果是基地,那么
//您可以“等待”并开始新的周期,而不是引发异常。
引发异常.Create('分解时无法创建新实例');
继承创造;
TBase.Instances.Add(Self);
结束;
类程序TBase.Debug;
开始
Writeln('TBase.Instances.Count:',TBase.Instances.Count);
结束;
析构函数TBase.Destroy;
开始
如果不是的话,那就销毁
开始
t基本销毁:=真;
尝试
t基本清晰(自我);
最后
t基本销毁:=假;
结束;
结束;
继承;
结束;
类析构函数TBase.Destroy;
开始
t基本清除(无);
TBase.Instances.Free;
结束;
过程CreateItems(计数:整数);
变量
I:整数;
项目:滴度;
开始
TBase.Debug;
对于I:=0进行计数-1 do
创造;
TBase.Debug;
项目:=TItem.Create;
TBase.Debug;
项目.免费;
TBase.Debug;
结束;
开始
ReportMemoryLeaksOnShutdown:=True;
尝试
创建项目(100);
除了
关于E:Exception-do
Writeln(E.ClassName,“:”,E.Message);
结束;
Writeln(“按完成”);
ReadLn;
结束。
Barry,谢谢你的回答,但也许你不明白我想说什么。我在TSecondClass上有很多对象类型的字段,我想在TBaseClass上实现疯狂的析构函数来释放所有的子字段,而不用手动操作。我知道在NIL实例上调用Free并不重要,但在基类中,我不知道哪些字段是在子类上创建的,对吗?这就是我想在TBaseClass.Destroy上做的。对所有的类都遵循相同的规则,所有的东西都会被销毁。你能添加一个小的('pseudo'-)代码示例吗
program BaseFree;
{$APPTYPE CONSOLE}
uses
Classes, SysUtils;
type
TBase = class(TObject)
strict private
class var Instances: TList;
class var Destroying: boolean;
class procedure Clear(Instance: TBase);
private
class procedure Debug;
protected
class constructor Create;
class destructor Destroy;
public
constructor Create;
destructor Destroy; override;
end;
TItem = class(TBase)
end;
{ TBase }
class procedure TBase.Clear(Instance: TBase);
var
I: Integer;
Item: TBase;
begin
for I := 0 to TBase.Instances.Count -1 do
begin
Item:= TBase.Instances[I];
if Item <> Instance then
Item.Destroy;
end;
TBase.Instances.Clear;
end;
class constructor TBase.Create;
begin
TBase.Instances:= TList.Create;
end;
constructor TBase.Create;
begin
if TBase.Destroying then
// instead of raise Exception, you can "wait" and start a new cycle.
raise Exception.Create('Cannot Create new instances while Destrying.');
inherited Create;
TBase.Instances.Add(Self);
end;
class procedure TBase.Debug;
begin
Writeln('TBase.Instances.Count: ', TBase.Instances.Count);
end;
destructor TBase.Destroy;
begin
if not TBase.Destroying then
begin
TBase.Destroying:= True;
try
TBase.Clear(Self);
finally
TBase.Destroying:= False;
end;
end;
inherited;
end;
class destructor TBase.Destroy;
begin
TBase.Clear(nil);
TBase.Instances.Free;
end;
procedure CreateItems(Count: Integer);
var
I: Integer;
Item: TItem;
begin
TBase.Debug;
for I := 0 to Count -1 do
TItem.Create;
TBase.Debug;
Item:= TItem.Create;
TBase.Debug;
Item.Free;
TBase.Debug;
end;
begin
ReportMemoryLeaksOnShutdown:= True;
try
CreateItems(100);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Writeln('Press <ENTER> to finish.');
ReadLn;
end.