Weak references – or why you should enable ReportMemoryLeaksOnShutdown

I was enhancing my dzMdbViewer tool with a view that shows the table structure rather than the data of the table, when I out of curiosity added

  ReportMemoryLeaksOnShutdown := True;

to the source code. I found some memory leaks that were easy to fix (and which I would probably have fixed anyway later when reviewing the code).

I continued working on the new functionality and used some rather old code I wrote about 10 years ago (part of dzlib, but only used in a special tool) for it. It allows reading the table structure of an MS Access database and puts it into a structure of interfaces. Everything worked fine, until I closed the program and got gazillions of memory leak reports.

It turned out that back then I had created circular references between these interfaces: Tables were referencing Indexes and Columns, Indexes were referencing Tables and Columns, Columns were referencing Tables and Indexes. Of courses, this had to create memory leaks because the reference counter of a circular reference can never reach 0 so these objects were never freed.

There is a cure for this kind of problem: It’s called weak references. Since the Delphi compiler for desktop (Windows) does not support weak references (The mobile compiler supports it via a [weak] attribute, but that attribute is ignored by the desktop compiler.), it must be implemented in code. I am a lazy bastard, so I turned to Google for an answer. This is an ancient problem, so somebody was bound to have blogged about it. I found several hits on StackOverflow and also this Synopse blog post that covers the problem in depth.

So, the solution was this procedure:

procedure SetWeak(_InterfaceField: PIInterface; const _Value: IInterface);
begin
  PPointer(_InterfaceField)^ := Pointer(_Value);
end;

It is used like this:

type
  TChild = class(TInterfacedObject, IChild)
  private
    FParent: IParent; // This must be a weak reference!
  public
    constructor Create(_Parent: IParent);
    destructor Destroy; override;
  end;

constructor TChild.Create(_Parent: IParent);
begin
  inherited Create;
  SetWeak(@FParent, _Parent);
end;

destructor TChild.Destroy;
begin
  SetWeak(@FParent, Nil);
  inherited;
end;

Assuming that TParent (which implements IParent) maintains a strong reference to its TChild objects via IChild interfaces, but the TChild object also needs a reference to its parent (via an IParent interface), we get a circular reference. To solve this, the FParent reference is declared as a weak reference (which is only a comment) and assigned via the SetWeak procedure. Now, assigning FParent does not increment the reference counter of the parent, so when there are no other references to it, it will get destroyed. When the parent gets destroyed all references to its children also get removed, which in turn triggers the child to be destroyed. The child still maintains the reference to the parent, but it is a weak reference, so it must not decrement the reference counter when FParent gets NILed. So, again, the destructor of TChild must use SetWeak to assign NIL to it.