【问题标题】:Delphi: Object aggregation and memory leaks using [weak] attributeDelphi:使用 [weak] 属性的对象聚合和内存泄漏
【发布时间】:2017-11-03 02:50:25
【问题描述】:

我想使用聚合构建一个包含多个子对象的类TParent。有些对象是独立的,而有些对象也可以依赖于其他子对象。所有子对象都必须具有对父对象的引用。我还想尽可能使用接口。

为此,我将TInterfacedObject 用于TParentTAggregatedObject 用于儿童。由于孩子和父母都彼此了解,我使用弱引用来避免循环依赖。事实上,这种行为已经在TAggregatedObject 中定义。当我只使用独立的子对象 (TIndependantChild) 时,一切正常。

当子对象也依赖其他子对象时会出现问题,请参阅TDependantChild 的构造函数。我将另一个子对象的引用存储在 fChild 变量中,该变量标有 [weak] 属性,在 Delphi 10 Berlin 中引入。 FastMM4 在关机时报告内存泄漏:

还会引发导致System.TMonitor.Destroy 的访问冲突,但这仅在使用 FastMM4 且 ReportMemoryLeaksOnShutDown 为 True 时发生。

program Project1;

{$APPTYPE CONSOLE}

uses
  FastMM4,
  System.SysUtils;

type
  IParent = interface
  ['{B11AF925-C62A-4998-855B-268937EF30FB}']
  end;

  IChild = interface
  ['{15C19A4E-3FF2-4639-8957-F28F0F44F8B4}']
  end;

  TIndependantChild = class(TAggregatedObject, IChild)
  end;

  TDependantChild = class(TAggregatedObject, IChild)
  private
    [weak] fChild: IChild;
  public
    constructor Create(const Controller: IInterface; const AChild: IChild); reintroduce;
  end;

  TParent = class(TInterfacedObject, IParent)
  private
    fIndependantChild: TIndependantChild;
    fDependantChild: TDependantChild;
  public
    constructor Create;
    destructor Destroy; override;
  end;

{ TParent }

constructor TParent.Create;
begin
  fIndependantChild := TIndependantChild.Create(Self);
  fDependantChild := TDependantChild.Create(Self, fIndependantChild);
end;

destructor TParent.Destroy;
begin
  fDependantChild.Free;
  fIndependantChild.Free;
  inherited;
end;

{ TDependantChild }

constructor TDependantChild.Create(const Controller: IInterface; const AChild: IChild);
begin
  inherited Create(Controller);
  fChild := AChild;
end;

var
  Owner: IParent; 

begin
  ReportMemoryLeaksOnShutDown := True;
  Owner := TParent.Create;
  Owner := nil;
end.

我发现,使用 [unsafe] 而不是 [weak] 可以解决问题,但根据 delphi help

它([unsafe])应该只在极少数情况下在系统单元之外使用。

因此,我不相信我应该在这里使用[unsafe],尤其是当我不明白会发生什么时。

那么,这种情况下内存泄漏的原因是什么,如何克服呢?

【问题讨论】:

  • 为什么需要对孩子进行聚合?你了解聚合实际上是什么吗?为什么要混合对象引用和接口?这总是灾难的根源。使用调试器查看内存泄漏的原因。
  • [weak] 是在柏林 10.1 中为接口添加的,但 [weak] 存在于早期版本中。我可以在 XE2 中按原样编译您的代码,但 [weak] 无效。 Owner 具有 RefCount=1,因为在分配 fChild 时,TDependantChild 具有对 TParent 的非弱引用(由于聚合),尽管是 [weak]。当Owner 被销毁时,TInterfacedObject.BeforeDestruction()RefCount<>0 时引发错误,导致Owner 及其子项被泄露。将 fChild 更改为 Pointer 可以解决此问题。在您使用[unsafe] 时,我怀疑您的情况类似。用调试器确认
  • 雷米,感谢 cmets。我对聚合概念很陌生,可能是我理解错了。在我的情况下,子对象代表某些类功能。没有Parent,它们就没有意义,但是可以使用相同的子对象来组成不同的父对象。我也不喜欢混合接口和对象引用,但我发现了几个这样做的例子link,并且在仅使用接口时会引发错误。我会尽快检查第二条评论。
  • 我将调试器断点放在procedure TInterfacedObject.BeforeDestruction。没有[weak]RefCount 确实为 1,而使用[weak],则为 0。看来,它在其他地方泄露了。

标签: delphi memory-leaks aggregation unsafe weak


【解决方案1】:

使用外部 FastMM4 内存管理器时的泄漏和崩溃问题与以下有关用于跟踪弱引用的内部 HashMap 的最终确定问题有关。

[REGRESSION XE2/10.1 Berlin] Unable to use 3rd party memory managers

由于该问题,无法在 Delphi 10.1 和更新版本(包括外部 FastMM4)中使用 3rd 方内存管理器进行泄漏检测。

这就是您对[weak] 属性有问题而对[unsafe] 没有问题的原因。


就您的代码而言,您可以在上述场景中安全地使用[unsafe]。虽然文档中有关于使用 [unsafe] 属性的警告,但该警告实际上并不能解释为什么不应使用 [unsafe]

长话短说,当[unsafe] 引用所引用的对象实例的生命周期长于引用本身的生命周期时,您可以使用[unsafe] 属性。

换句话说,您必须确保在 [unsafe] 指向的对象实例被释放后,您不会访问它,仅此而已。

[unsafe] 引用在其指向的对象被销毁时不会清零,在对象消失后使用此类引用将导致访问冲突异常。

[weak] 属性替换为[unsafe] 是您所要做的所有事情,以使您的代码具有正确的功能。

  TDependantChild = class(TAggregatedObject, IChild)
  private
    [unsafe] fChild: IChild;

【讨论】:

  • 感谢@Dalija 提供了非常明确的答案!我花了几天时间试图了解发生了什么。我还在另一个相关讨论中添加了link,我在您提供的链接中找到了该讨论。使用 FastMM4,此示例项目会生成与此处讨论的相同的访问冲突错误。
猜你喜欢
  • 2012-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多