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
Post a Comment