Zeroing Weak Object References

In the two-part post series, The purpose of weak references - Part I and The purpose of weak references - Part II, I wrote about the purpose of weak references in automatic reference counting, as well as in manual memory management, where they are commonly referred to as non-owning references.

In ARC, object references to reference-counted objects are unsafe, non-zeroing weak references, and in manual memory management, non-owning references to an object instance are also unsafe.

Unsafe, in the above context, means that you can safely use such references only if the object instance they point to always has a longer lifetime than the unsafe reference, or that there is additional mechanism (code) that will notify you when the object is destroyed, so you know the reference is not pointing to a valid object anymore.

In manual memory management, TComponent implements such a notification mechanism. However, there are two downsides: first, it only works with TComponent descendants; and second, it requires a lot of wiring code in different places, compared to zeroing weak references in ARC, where you just need to mark the reference with the [weak] attribute.

Once you get used to zeroing weak references, you may want to have a similar, simple mechanism that will allow you to mark some object reference as a weak one, and when the object is destroyed, that reference would automatically be set to nil.

While it would be neat to have such support directly built into the compiler, where the [weak] attribute would also work for object references the same way it works for interface references, Delphi has all the necessary building blocks for implementing such a feature.

So what we want to achieve is the ability to convert the following code, where accessing instance behind ObjRef reference is no longer safe when the object is destroyed by assigning nil to Intf, to a code where ObjRef would be set to nil after that.

Note: Accessing the ObjRef as the pointer value is safe, but the value it holds is no longer pointing to valid, allocated memory.

procedure Foo; var Intf: IFoo; // strong reference ObjRef: TFoo; // unsafe weak reference begin Intf := TFoo.Create; ObjRef := TFoo(Intf); ... Intf := nil; // explicitly release the object (there are no other strong references) // ObjRef is now a dangling pointer, and the instance it points to // must not be used after this point Writeln(Assigned(ObjRef)); // TRUE, although the object is gone end;

We want to achieve the following:

Note: This code does not actually work, it is just what we would like to get

procedure Foo; var Intf: IFoo; // strong reference [weak] ObjRef: TFoo; // zeroing weak reference begin Intf := TFoo.Create; ObjRef := TFoo(Intf); ... Intf := nil; // explicitly release the object (there are no other strong references) Writeln(Assigned(ObjRef)); // FALSE end;

Such a feature is implemented in Spring4D through Spring.Weak, and just by changing the type of ObjRef to Spring.Weak<TFoo>, you will get all the bells and whistles of a zeroing weak reference:

procedure Foo; var Intf: IFoo; // strong reference ObjRef: Spring.Weak<TFoo>; // zeroing weak reference begin Intf := TFoo.Create; ObjRef := TFoo(Intf); Intf := nil; // explicitly release the object (there are no other strong references) Writeln(ObjRef.IsAlive); // FALSE end;

This feature can be used not only on reference-counted objects, but also on regular objects that are manually managed:

procedure Foo; var Obj: TFoo; // owning reference ObjRef: Spring.Weak<TFoo>; // zeroing weak reference begin Obj := TFoo.Create; ObjRef := Obj; Obj.Free; // or FreeAndNil(Obj); Writeln(ObjRef.IsAlive); // FALSE end;

You can also use a simpler—do not read this as "optimized"—but easier implementation to follow: https://github.com/dalijap/code-delphi-mm/tree/master/Part5/Weak


Comments

Popular posts from this blog

Coming in Delphi 12: Disabled Floating-Point Exceptions

Assigning result to a function from asynchronous code

Beware of loops and tasks