Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Weak reference": down to earth explanation needed

Can someone provide an explanation of a weak reference in Delphi?

I noticed that the concept is often mentioned in some library/framework source code I scrutinize. I'm in a limbo and want to have a clear cut understanding of it.

like image 262
menjaraz Avatar asked Jul 16 '12 09:07

menjaraz


2 Answers

By default in Delphi, all references are either:

  • weak references for pointer and class instances;
  • explicit copy for low-level value types like integer, Int64, currency, double or record (and old deprecated object or shortstring);
  • copy-on-write with reference counting for high-level value types (e.g. string, widestring, variant or a dynamic array);
  • strong reference with reference counting for interface instances;

The main issue with strong reference counting is the potential circular reference problem. This occurs when an interface has a strong reference to another, but the target interface has a strong pointer back to the original. Even when all other references are removed, they still will hold on to one another and will not be released. This can also happen indirectly, by a chain of objects that might have the last one in the chain referring back to an earlier object.

See the following interface definition for instance:

  IParent = interface
    procedure SetChild(const Value: IChild);
    function GetChild: IChild;
    function HasChild: boolean;
    property Child: IChild read GetChild write SetChild;
  end;

  IChild = interface
    procedure SetParent(const Value: IParent);
    function GetParent: IParent;
    property Parent: IParent read GetParent write SetParent;
  end;

The following implementation will definitively leak memory:

procedure TParent.SetChild(const Value: IChild);
begin
  FChild := Value;
end;

procedure TChild.SetParent(const Value: IParent);
begin
  FParent := Value;
end;

In Delphi, most common kind of reference-copy variables (i.e. variant, dynamic array or string) solve this issue by implementing copy-on-write. Unfortunately, this pattern is not applicable to interface, which are not value objects, but reference objects, tied to an implementation class, which can't be copied.

Note that garbage collector based languages (like Java or C#) do not suffer from this problem, since the circular references are handled by their memory model: objects lifetime are maintained globally by the memory manager. Of course, it will increase memory use, slowdown the process due to additional actions during allocation and assignments (all objects and their references have to be maintained in internal lists), and may slow down the application when garbage collector enters in action.

One common solution with languages with no garbage-collection (like Delphi) is to use Weak pointers, by which the interface is assigned to a property without incrementing the reference count. In order to easily create a weak pointer, the following function could be used:

procedure SetWeak(aInterfaceField: PIInterface; const aValue: IInterface);
begin
  PPointer(aInterfaceField)^ := Pointer(aValue);
end;

Therefore, it could be used as such:

procedure TParent.SetChild(const Value: IChild);
begin
  SetWeak(@FChild,Value);
end;

procedure TChild.SetParent(const Value: IParent);
begin
  SetWeak(@FParent,Value);
end;

You can try to read my blog post about weak references in Delphi - and its associated source code: we have implemented direct weak reference, and "zeroing" weak reference interface handling from Delphi 6 up to XE2.

In fact, in some cases, you'll need to set the interface weak fields to nil, if you release the reference instance before its child, to avoid any Access Violation issue. This is called "Zeroing Weak pointers", and what Apple implemented with the ARC model, and that we tried to implement in Delphi.

like image 40
Arnaud Bouchez Avatar answered Sep 17 '22 04:09

Arnaud Bouchez


Instances that reference each other by interface references keep each other alive in a reference count based interface implementation.

A weak reference is used to break the "keep each other alive" bear hug. This is done by declaring one reference as a pure pointer to circumvent the reference counting mechanism.

IFriend = Interface(IInterface)
end;

TFriend = class(TInterfacedObject, IFriend)
private
  FFriend: IFriend;
end;


var
  Peter: IFriend;
  John: IFriend;
begin
  Peter := TFriend.Create;
  John := TFriend.Create;

  Peter.Friend := John;
  John.Friend := Peter;
end;

Even when Peter and John go out of scope, their instances are kept around because their mutual reference keeps their refcount from dropping to zero.

The problem is more commonly found in composite patterns (parent - child relationships) where the child has a back reference to the parent:

ISomething = Interface(IInterface)
end;

TSomething = class(TInterfacedObject, ISomething)
end;

TParent = class(TSomething)
  FChildren: TInterfacedList;
end;

TChild = class(TSomething)
  FParent: ISomething;
end;

Again, parent and child can keep eachother around because their mutual reference keeps their refcount from dropping to zero.

This is solved with a weak reference:

TChild = class(TSomething)
  FParent: Pointer;
end;

By declaring the FParent as a "pure" pointer the reference counting mechanism doesn't come into play for the back reference to the parent. When a parent goes out of scope, its reference count now can drop to zero because its children no longer keep its ref count above zero.

Note This solution does require careful attention to lifetime management. Children can be kept alive beyond the life time of the parent when something on the "outside" of these classes keeps a reference to a child. And this can lead to all sorts of interesting AV's when the child assumes the parent reference always points to a valid instance. If you need it, make sure that when the parent goes out of scope, it makes the children nil their back references before it nils its own references to its children.

like image 182
Marjan Venema Avatar answered Sep 20 '22 04:09

Marjan Venema