如何免费嵌套(对象类型)字段类?

时间:2011-07-02 02:32:30

标签: delphi oop rtti

TBaseClass = class
public
  destructor Destroy; override;
end;

TFirstClass = class(TBaseClass)
  FMyProp: string;
end;

TSecondClass = class(TBaseClass)
  FMyFirstClass: TFirstClass;
end;

我需要实现一个能够从中找到所有(对象类型)字段的DESTRUCTOR 相同的基类,并给它一个免费的,以避免所有这些内存泄漏。

为什么呢?因为FMyFirstClass可以创建或不创建,这取决于我的应用程序的流程,我无法保证何时将其创建为Free it,也不想用NIL检查所有析构函数填充代码,因为我有一个很多像这样的领域。

我正在尝试使用新的RTTI来获取基于TBaseClass的所有字段,但我无法获得对象字段的实例,而且我没有想法。

我会走正路吗?你建议做什么?

5 个答案:

答案 0 :(得分:8)

调用Free对nil实例没有影响。它是故意设计的。你的析构函数应该在它逻辑上拥有的任何对象类型的字段上调用Free,与对象是否被构造无关。

答案 1 :(得分:3)

Delphi对象应始终由另一个对象“拥有”,并且所有者对象负责在调用Destructor时释放拥有的对象。由于这是为每个班级完成的,因此不应该有任何问题。你可以使用RTTI,但它只是时间和资源的一部分,因为编写Destructors非常简单。

示例,基类引入了两个TObject类型字段:

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;

引入新字段的后代类应覆盖析构函数并释放这些对象,然后调用inherited以使父级有机会释放它引入的对象。例如:

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;

在你的问题中你说:

  

我需要实现一个DESTRUCTOR,它能够从同一个基类中找到所有(对象类型)字段并给它一个Free来避免所有这些内存泄漏。

正如您所看到的,这不是这样做的方法:派生类的析构函数调用inherited,因此基类可以释放它引入的字段。

  • 正如Barry Kelly所说,在致电nil之前,您无需检查.Free,因为Free会自行完成。
  • 正如David Heffernan所说,你需要做的就是到处都遵循这种模式,你会没事,没有内存泄漏。这就是VCL一直以来的工作方式!
  • 正如mjn所说,注意不要在没有释放它们的情况下用其他对象覆盖拥有的对象:当你这样做时,你会丢失对旧对象的最后一个引用而你不能再释放它,这是一个保证的内存泄漏。

答案 2 :(得分:1)

我认为你没有采取正确的方式。只需释放您声明/创建它们的类中的对象。正如巴里所说,你不需要进行零检查。

答案 3 :(得分:1)

您在其他答案中收到的建议是正确的。确保将每个对象实例化与析构函数中的相应Free配对。

我想指出为什么你不能使用RTTI来查找所有对象实例并释放它们。某些对象实例可能不属于正在销毁的对象。如果您的字段包含对系统中某个其他实体所拥有的对象的引用,那么您无权将其销毁。 RTTI无法告诉您谁拥有该对象,只有您可以知道。

答案 4 :(得分:-1)

你这样做是错误的。您必须保存引用以了解您必须释放的内容,如果您不知道流程,那么您的设计就是错误。

如果您的需求不同寻常,您可以重构基类来保存所有引用,检查此代码是否可以帮助您:

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.