【问题标题】:Recursive Destructor递归析构函数
【发布时间】:2016-03-13 14:14:15
【问题描述】:

我有以下两个类:

type
  TItemProp = class
  private
    FItemPropName: string;
    FItemPropValue: string;
  public
    constructor Create(AItemPropName, AItemPropValue: string);
    class function GetItemProp(AHTMLElement: OleVariant; AProp: string): TItemProp;
    property ItemPropName: string read FItemPropName;
    property ItemPropValue: string read FItemPropValue;
 end;

TTest = class
private
  FName: string;
  FProps: TList<TItemProp>;
  FTest: TList<TTest>;
public
  constructor Create(AName: string);
  destructor Destoroy();
  property Name: string read FName;
  property ItemProps: TList<TItemProp> read FProps write FProps;
  property Test: TList<TTest> read FTest write FTest;
end;

这里是TTest类的构造函数和析构函数的代码:

constructor TTest.Create(AName: string);
begin
  Self.FName := AName;
  Self.FProps := TList<TItemProp>.Create();
  Self.FTest := TList<TTest>.Create();
end;

destructor TTest.Destoroy();
var
  I: Integer;
begin
  for I := 0 to Self.FTest.Count - 1 do
  begin
    Self.FTest[I].Free;
    Self.FTest[I] := nil;
  end;

  Self.FProps.Free;
  Self.FTest.TrimExcess;
  Self.FTest.Free;
  inherited; 
end;

问题是这段代码正在泄漏内存。我应该如何重写析构函数以修复内存泄漏?

【问题讨论】:

  • 这个类是否拥有包含在FProps 中的TItemProps?如果是,我看不到您释放这些对象。考虑改用TObjectList&lt;TItemProp&gt;TObjectList&lt;TTest&gt; - 默认情况下这些是用[doOwnsValues] 创建的,列表本身将管理从中删除的释放对象。
  • 我恢复了您的编辑,它提出了一个新问题。请不要那样做。

标签: delphi memory-leaks destructor


【解决方案1】:

第一个问题在这里:

destructor Destroy();

您需要覆盖TObject 中声明的虚拟析构函数。像这样:

destructor Destroy; override;

您的析构函数实现是不必要的复杂,并且也无法销毁FProps 拥有的对象。这个析构函数应该这样写:

destructor TTest.Destroy;
var
  I: Integer;
begin
  for I := 0 to FTest.Count - 1 do
    FTest[I].Free;
  FTest.Free;

  for I := 0 to FProps.Count - 1 do
    FProps[I].Free;
  FProps.Free;

  inherited; 
end;

您的公开这些列表对象的属性不应具有 setter 方法。所以,它们应该是这样的只读属性:

property ItemProps: TList<TItemProp> read FProps;
property Test: TList<TTest> read FTest;

如果任何主体尝试写入这些属性,您的代码将导致泄漏。

如果您使用TObjectList 而不是TList,您将能够将生命周期管理委托给列表类。这往往会导致代码更简单。

TList&lt;T&gt; 实例公开为公共属性确实会使您的类暴露在滥用之下。此类的客户可以调用该类的任何公共方法,并可能以您不期望且不希望迎合的方式改变列表。您应该寻求更多地封装这些对象。

作为一般规则,您应该始终调用类的继承构造函数。在您的情况下,构造函数是 TObject 构造函数,它什么都不做。但是,无论如何调用它是一个好习惯。这样做意味着如果您在以后更改继承层次结构,您将不会因为不调用新父类的构造函数而被抓到。

constructor TTest.Create(AName: string);
begin
  inherited;
  FName := AName;
  FProps := TList<TItemProp>.Create();
  FTest := TList<TTest>.Create();
end;

您在任何地方都在使用Self。这很好,但它根本不是惯用的。我建议您不要养成这种习惯,否则您的代码会非常冗长且不易阅读。

您发布的代码中有大量错误。您的程序中显然有更多您尚未发布的代码。我非常希望该代码也包含错误,因此即使在应用上述所有更改后仍然存在泄漏,请不要感到惊讶。

【讨论】:

  • 我应该实现将对象添加到私有列表的过程,对吧?
  • 嗯,你仍然可以调用属性暴露的列表对象的方法。但是,将所有内容都公开,就是在要求滥用。
  • 当使用 for 循环从列表中删除对象时,我虽然你需要做 for I := FTest.Count - 1 downto 0 do
  • @ALombardo 如果您要删除它们。但是这里的代码并没有这样做。再读一遍,这次更仔细。对象不会被移除,而是被销毁。
  • 当然你应该使用 Free 而不是 Destroy 作为一般规则。
猜你喜欢
  • 2015-07-01
  • 2017-06-12
  • 2011-03-05
  • 2012-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-31
相关资源
最近更新 更多