Dealing with circular strong references in Delphi

后端 未结 4 1147
隐瞒了意图╮
隐瞒了意图╮ 2021-01-18 16:57

I got two classes (in my example TObject1 and TObject2) which know each other via interfaces (IObject1, IObject2). As you probably know in Delphi this will lead to a memory

4条回答
  •  梦毁少年i
    2021-01-18 17:39

    Don't use unsafe
    [unsafe] should not be used in normal code.
    It is really a hack to the used if you don't want the compiler to do reference counting on interfaces.

    Use weak instead
    If for some reason you must have circular references then use a [weak] attribute on one of the references and declare the other reference as usual.

    In your example it would look like this:

      TParent = class(TInterfacedObject, IParent)
        FChild: IChild;   //normal child
        constructor Create;
        function GetObject2: IChild;
      end;
    
      TChild = class(TContainedObject, IChild)
        //reference from the child to the parent, always [weak] if circular.
        [weak] FObj1: IParent;   
        constructor Create(const aObj1: IParent);
      end;
    

    Now there is no need to do anything special in the destructors, so these can be omitted.
    The compiler tracks all weak references and sets them to nil when the reference count of the referenced interface reaches zero.
    And all this is done in a thread-safe manner.
    However the weak reference itself does not increase the reference count.

    When to use unsafe
    This is in contrast to the unsafe reference, where no tracking and no reference counting at all takes place.

    You would use an [unsafe] reference on an interfaced type that is a singleton, or one that has disabled reference counting.
    Here the ref count is fixed at -1 in any case, so the calling of addref and release is an unneeded overhead.
    Putting the [unsafe] eliminates that silly overhead.
    Unless your interfaces override _addref and _release do not use [unsafe].

    Pre Berlin alternative
    Pre Berlin there is no [weak] attribute outside the NexGen compilers.
    If you are running Seattle, 2010 or anything in between the following code would do {almost} the same.
    Although I'm unsure if this code might not fall victim to race conditions in multithreaded code.
    If that's a concern for you feel free to raise a flag and I'll investigate.

      TParent = class(TInterfacedObject, IParent)
        FChild: IChild;   //normal child
        constructor Create;
        function GetObject2: IChild;
      end;
    
      TChild = class(TContainedObject, IChild)
        //reference from the child to the parent, always [weak] if circular.
        FObj1: TParent;   //not an interface will not get refcounted.  
        constructor Create(const aObj1: IParent);
        destructor Destroy; override;
      end;
    
      constructor TChild.Create(const aObj1: IParent);
      begin
        inherited Create;
        FObject1:= (aObj1 as TParent);
      end;
    
     destructor TParent.Destroy;
     begin
       if Assigned(FChild) then FChild.InvalidateYourPointersToParent(self);
       inherited;
     end;
    

    This will also ensure the interfaces get properly disposed, however now TChild.FObject1 will not automatically get nilled. You might be able to put code in the destructor of the TParent to visit all its children and inform them as in the code shown.
    If one of the participants in the circular reference can't inform its weakly linked counterparts then you'll need to setup some other mechanism to nil those weak references.

提交回复
热议问题