8

I want to build a class TParent containing several child objects by using aggregation. Some of the objects are independant, while some can be also dependant on the other children. All the children objects have to have a reference to the parent. I also want to use the interfaces where possible.

For this purpose I am using TInterfacedObject for the TParent and TAggregatedObject for the children. As both the child and the parent know about each other, I am using weak references in order to avoid the circuar dependency. In fact this behaviour is already defined in TAggregatedObject. Everything works fine when I am using only the independant child objects (TIndependantChild).

The problem arises when the child object depends on the other children too, see the constructor of TDependantChild. I store the reference to the another child object in fChild variable, which is marked with the [weak] attibute, introduced in Delphi 10 Berlin. FastMM4 reports memory leaks on shutdown:

enter image description here

Also access violation leading to System.TMonitor.Destroy raises, but this happens only when FastMM4 is in the uses and ReportMemoryLeaksOnShutDown is 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.

I found, that using [unsafe] instead of [weak] solves the problem, but according to the delphi help

It ([unsafe]) should be only used outside the System unit in very rare situations.

Therefore, I am not convinced, that I should use [unsafe] here, especially when I don't understand what happens.

So, what are the reassons of the memory leaks in this situation and how to overcome them?

LU RD
  • 34,438
  • 5
  • 88
  • 296
VyPu
  • 83
  • 5
  • 1
    Why do the children need to be aggregated? Do you understand what aggregation actually is? Why are you mixing object references and interfaces? That is always a recipe for disaster. Use the debugger to see why you are leaking memory. – Remy Lebeau Nov 03 '17 at 03:29
  • 1
    `[weak]` was added for interfaces in 10.1 Berlin, but `[weak]` exists in earlier versions. I can compile your code as-is in XE2, but the `[weak]` has no effect. `Owner` has `RefCount=1` because `TDependantChild` has a non-weak reference to `TParent` (due to aggregation) when `fChild` is assigned, despite being `[weak]`. When `Owner` is destroyed, `TInterfacedObject.BeforeDestruction()` raises an error when `RefCount<>0`, causing `Owner` and its children to be leaked. Changing `fChild` to `Pointer` fixes that. I suspect something similar in your case when using `[unsafe]`. Confirm with debugger – Remy Lebeau Nov 03 '17 at 03:47
  • Remy, Thanks for the comments. I am new to the aggregation concept, it might be, that I understood it wrong. In my case the child objects represent certain class functionality. They have no sence without the Parent, but the same child objects can be used to compose different parents. I did not like mixing interfaces and object references too, but I found several examples where it was done so [link](https://stackoverflow.com/questions/3483680/delphi-how-delegate-interface-implementation-to-child-object), and it raised errors when using only interfaces. I will check the second commment soon. – VyPu Nov 03 '17 at 09:07
  • I put the debugger breakpoint at `procedure TInterfacedObject.BeforeDestruction`. Without `[weak]` the `RefCount` is indeed 1, and with `[weak]` it is 0. So it seems, that it is leaked somewhere else. – VyPu Nov 03 '17 at 10:47

1 Answers1

4

Issue with the leak and the crash when you use external FastMM4 memory manager is related to following issue concerning finalization of internal HashMap used for tracking weak references.

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

Due to that issue it is impossible to use 3rd party memory managers for leak detection in Delphi 10.1 and newer versions, and including external FastMM4.

That is the reason why you have issues with [weak] attribute and you don't have issues with [unsafe].


As far as your code is concerned, you can safely use [unsafe] in above scenario. While there is warning in documentation about using [unsafe] attribute, that warning actually does not explain why [unsafe] should not be used.

To make long story short, you can use [unsafe] attribute when lifetime of object instance referenced by [unsafe] reference is longer than the lifetime of the reference itself.

In other words, you must ensure that you don't access [unsafe] reference after the object instance it points to has been released and that is all.

[unsafe] references are not zeroed out when the object they point to is destroyed, and using such reference after the object is gone will result in access violation exceptions.

Replacing the [weak] attribute with the [unsafe] is all you have to do in order to have properly functional code as you presented it.

  TDependantChild = class(TAggregatedObject, IChild)
  private
    [unsafe] fChild: IChild;
Dalija Prasnikar
  • 27,212
  • 44
  • 82
  • 159
  • Thank you @Dalija for a very clear answer! I spend days while trying to understand what happens. I also add a [link](https://github.com/pleriche/FastMM4/issues/18) to another related discussion, which I found on your provided link. With FastMM4 this example project generates the same access violation error as discussed there. – VyPu Nov 04 '17 at 21:46