“Weak reference”: down to earth explanation needed

后端 未结 4 383
暗喜
暗喜 2021-02-04 03:17

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 scrutini

4条回答
  •  花落未央
    2021-02-04 03:57

    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.

提交回复
热议问题