“Weak reference”: down to earth explanation needed

后端 未结 4 376
暗喜
暗喜 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:50

    In a most general case a strong reference controls the lifetime of a referenced instance while a weak reference does not. The term weak reference can be used in context of a garbage collector, reference counted interfaces or common objects.

    For example, a Delphi form hold the references to all its controls; these references can be called strong because when a form is destroyed its controls are also destroyed. On the other hand, a Delphi form's control has a reference to a form it belongs to. This reference can be call weak because it does not controls a form's lifetime in any way.

    0 讨论(0)
  • 2021-02-04 03:51

    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.

    0 讨论(0)
  • 2021-02-04 03:51

    See also

    Automatic Reference Counting in Delphi Mobile Compilers

    which includes documentation of the new [weak] attribute:

    Another important concept for ARC is the role of weak references, which you can create by tagging them with [weak] attribute.

    0 讨论(0)
  • 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.

    0 讨论(0)
提交回复
热议问题