处理Delphi中的循环强引用 [英] Dealing with circular strong references in Delphi

查看:121
本文介绍了处理Delphi中的循环强引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个类(在我的示例中为TObject1和TObject2),它们通过接口(IObject1,IObject2)相互了解。正如您在Delphi中可能知道的那样,这将导致内存泄漏,因为参考计数器将始终保持在零以上。通常的解决方案是将一个引用声明为弱。这在大多数情况下都有效,因为你通常知道哪一个会被破坏,或者一旦它被销毁就不一定需要弱参考后面的对象。

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 leak as both reference counter will always stay above zero. The usual solution is declaring one reference as weak. This works in most cases because you usually know which one will be destroyed first or don't necessarily need the object behind the weak reference once it is destroyed.

这就是说我试图解决问题的方式是两个对象都保持活着,直到两个对象都不再被引用:(当我使用[unsafe]属性时需要Delphi 10.1)

This said I tried to solve the problem in a manner that both objects stay alive until both aren't referenced anymore: (Delphi 10.1 required as I use the [unsafe] attribute)

program Project14;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

type
  IObject2 = interface;

  IObject1 = interface
    ['{F68D7631-4838-4E15-871A-BD2EAF16CC49}']
    function GetObject2: IObject2;
  end;

  IObject2 = interface
    ['{98EB60DA-646D-4ECF-B5A7-6A27B3106689}']
  end;

  TObject1 = class(TInterfacedObject, IObject1)
    [unsafe] FObj2: IObject2;
    constructor Create;
    destructor Destroy; override;

    function GetObject2: IObject2;
  end;

  TObject2 = class(TContainedObject, IObject2)
    [unsafe] FObj1: IObject1;
    constructor Create(aObj1: IObject1);
    destructor Destroy; override;
  end;

constructor TObject1.Create;
begin
  FObj2 := TObject2.Create(Self);
end;

destructor TObject1.Destroy;
begin
  TContainedObject(FObj2).Free;
  inherited Destroy;
end;

function TObject1.GetObject2: IObject2;
begin
  Result := FObj2;
end;

constructor TObject2.Create(aObj1: IObject1);
begin
  inherited Create(aObj1);
  FObj1 := aObj1;
end;

destructor TObject2.Destroy;
begin
  inherited Destroy;
end;

function Test1: IObject1;
var
  x: IObject2;
begin
  Result := TObject1.Create;
  x := Result.GetObject2;
end;

function Test2: IObject2;
var
  x: IObject1;
begin
  x := TObject1.Create;
  Result := x.GetObject2;
end;

var
  o1: IObject1;
  o2: IObject2;
begin
  try
    o1 := Test1();
    o2 := Test2();
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

这确实有效..函数Test1和Test2分别创建TObject1和TObject2引用的一个实例一旦o1和o2超出范围,彼此和所有实例都会被销毁。该解决方案基于TContainedObject,它将引用计数转发给控制器(在这种情况下为TObject1)。

This does work as it is.. function Test1 and Test2 each create one instance of TObject1 and TObject2 referencing each other and all instances get destroyed once o1 and o2 go out of scope. The solution is based on TContainedObject which forwards the refcounting to the "controller" (TObject1 in this case).

现在我知道这个解决方案有缺陷,这就是我的地方问题开始:

Now I know this solution has flaws, and this is where my questions start:


  • TContainedObject(FObj2).Free;闻起来有点,但我没有更好的解决方案,因为我需要使用一个接口来引用TObject2(生产代码在这一端包含一些继承)。有什么想法来清理吗?

  • 你很容易忘记宣布两个类之间的所有引用都是弱的..

  • 一个类似的问题开始引发更多类:拥有一个引用的TObject3并引用另一个:内存泄漏。我可以通过让它从TContainedObject下降来处理它,但是使用遗留代码这可能不是一件容易的事。

我有这种感觉这个解决方案不能普遍应用,并希望能够 - 或者可能是一个答案,它将描述为什么它很难甚至不可能有一个易于使用的100%解决方案来管理这种情况。
Imho,如果没有从域中引用它们而不必仔细考虑该域中的每个引用,那么拥有一定数量的对象会导致彼此破坏并不复杂。

I have the feeling this solution can't be applied universally and hoping for one which can - or maybe an answer that will describe why it is so hard or even impossible to have an easy to use 100%-solution to manage such situations. Imho it can't be to complicated to have a finite amount of object which destroy each other once they are not referenced from out of their domain without having to carefully think about every reference within this domain.

推荐答案

不要使用不安全的

[不安全] 不应该在普通代码中使用。

如果您不希望编译器在接口上进行引用计数,那么它实际上是一个hack。

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.

使用弱

如果由于某种原因你必须有循环引用,那么使用其中一个引用的[weak] 属性并像往常一样声明另一个引用。

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;

现在不需要在析构函数中做任何特殊操作,因此可以省略这些。 >
编译器跟踪所有弱引用,并在引用接口的引用计数达到零时将它们设置为nil。

所有这些都是以线程安全的方式完成的。

但弱引用本身并不会增加引用计数。

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.

您将对作为单例的接口类型或已禁用的接口类型使用 [unsafe] 引用引用计数。

这里引用计数在任何情况下都固定为-1,因此调用addref和release是一种不必要的开销。

放入 [unsafe] 消除了这种愚蠢的开销。

除非您的接口覆盖 _addref _release 不要使用 [unsafe]

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没有 [弱] 属性。

如果您正在运行西雅图,2010或以下代码之间的任何内容将{几乎}相同。

虽然我我不确定这段代码是否可能不会成为多线程代码中竞争条件的牺牲品。

如果你担心这个问题,你可以自由地举旗,我会调查。

  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;

这也将确保接口得到妥善处理,但现在 TChild.FObject1 不会自动获得批准。您可以将代码放在 TParent 的析构函数中,以访问其所有子代,并在显示的代码中通知它们。

如果其中一个循环引用中的参与者无法通知其弱链接的对应者,那么您将需要设置一些其他机制来使这些弱引用无效。

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.

这篇关于处理Delphi中的循环强引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆