处理Delphi中的循环强引用

时间:2016-05-09 15:24:52

标签: delphi interface reference-counting delphi-10.1-berlin

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

这就是说我尝试以两种对象保持活着的方式来解决问题,直到两者都不再被引用:(因为我使用[unsafe]属性需要Delphi 10.1)

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)。

现在我知道这个解决方案存在缺陷,这就是我的问题所在:

  • “TContainedObject(FObj2)。自由;”闻起来有点,但我没有更好的解决方案,因为我需要使用一个接口来引用TObject2(生产代码在这一端包含一些继承)。有什么想法来清理它吗?
  • 你很容易忘记将两个类之间的所有引用声明为弱和..
  • 类似的问题开始引发更多类:拥有一个引用的TObject3并引用另一个:内存泄漏。我可以通过让它从TContainedObject下降来处理它,但是使用遗留代码这可能不是一件容易的事。

我觉得这个解决方案不能普遍应用,并且希望有一个可以 - 或者可能是一个答案,它将描述为什么它很难甚至不可能有一个易于使用的100%解决方案来管理这样的解决方案的情况。 Imho,如果没有从域中引用它们而不必仔细考虑该域中的每个引用,那么拥有有限数量的对象就会变得非常复杂。

4 个答案:

答案 0 :(得分:1)

请勿使用不安全的
[unsafe]不应在正常代码中使用 如果您不希望编译器在接口上进行引用计数,那么对于使用它来说真的是一个hack。

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

在您的示例中,它看起来像这样:

  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 所有这些都是以线程安全的方式完成的。
但弱引用本身并不会增加引用计数。

何时使用不安全
这与不安全的参考形成对比,其中根本不进行跟踪和参考计数。

您将对作为单例的接口类型使用[unsafe]引用,或者对已禁用引用计数的类型使用[unsafe]引用。 在任何情况下,引用计数都固定为-1,因此调用addref和release是一种不必要的开销 放_addref可以消除这种愚蠢的开销 除非您的界面覆盖_release[unsafe],否则请勿使用[weak]

柏林之前的替代
在柏林之前,NexGen编译器之外没有 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; 属性 如果您正在运行西雅图,2010年或以下代码之间的任何内容将{几乎}相同 虽然我不确定此代码是否可能不会成为多线程代码中竞争条件的牺牲品 如果你担心这个问题,请随意举旗,我会调查。

TChild.FObject1

这也将确保接口得到妥善处理,但现在TParent不会自动获得批准。您可以将代码放在<input id="currency_factor" type="text" style="display:none;" value="20"/> 的析构函数中以访问其所有子代,并在显示的代码中通知它们。
如果循环引用中的某个参与者无法通知其弱链接的对应方,那么您将需要设置一些其他机制来忽略那些弱引用。

答案 1 :(得分:0)

如果你想让两个物体保持活着或死在一起,它们肯定是一个单一的物体。好吧,我知道两者都可能是由不同的人开发的,所以我会让它们成为一个引用计数的超级对象的成员,就像这样

type
  TSuperobject = class( TInterfaceObject, IObject1, iObject2 )
  private
    fObject1 : TObject1;
    fObject2 : TObject2;
  public
    constructor Create;
    destructor Destroy;
    function GetObject2: IObject2;
    etc.
  end;

etc.

细节应该是显而易见的。对object1或object2的任何引用都必须引用拥有对象(superobject.object1等),因此object1和object2本身不需要引用计数 - 即它们可以是常规对象,而不是接口对象,但实际上并不重要如果它们是引用计数,因为所有者将始终将引用计数加1(在这种情况下,您可能不需要superobject中的析构函数)。如果你将object1和object2作为引用对象离开,那么它们彼此之间的反对就会变弱。

答案 2 :(得分:0)

你在这里解决了错误的问题。

您的实际问题不在于强弱的参考,也不在于如何改进您的解决方案。您的问题不在于如何实现,而在于您正在实现的目标(想要实现)

首先直接解决您的问题:

  
      
  • &#34; TContainedObject(FObj2)。免费;&#34;闻起来有点香,但我没有更好的解决方案,因为我需要使用一个接口来引用TObject2   (生产代码在此端包含一些继承)。任何   清理它的想法?
  •   

你在这里做不了多少。您必须在Free上致电FObj2,因为TContainedObject本身不是托管类。

  
      
  • 你很容易忘记将两个类之间的所有引用声明为弱和..
  •   

你也无法做任何事情。它来自领土。如果你想使用ARC,你必须考虑循环引用。

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

你也不能做太多。如果您的设计确实是您想要的,那么您只需处理其复杂性。

现在,回到你首先遇到问题的原因。

您想要实现的目标(以及您使用示例代码完成的操作)通过抓取该层次结构中的任何对象引用来保持整个对象层次结构的活跃。

要改写一下,你有FormButton,并希望Form保持活着状态Button(因为Button本身不会运作)。然后,您希望将Edit添加到Form,并在抓取Edit时再次保持所有内容。

这里你的选择很少。

  • 保持这种破碎的设计并使用您的解决方案,因为您涉及的代码太多而且更改会很痛苦。如果你这样做,请记住,这最终是破坏设计,不要试图在其他任何地方重复它。

  • 如果您的层次结构中TObject1是包含所有其他内容的根类,则重构它并从TObject2继承TInterfacedObject以拥有自己的引用计数并且不要; t抓住对FObj2的引用。而是抓住根TObject1实例并传递它,如果你真的需要。

  • 这是第二种方法的变体。如果TObject1不是根类,那么创建包含您需要的所有实例的附加包装类并传递它。

最后两个解决方案远非完美,他们没有处理你可能有太多或类似的课程的事实。但无论代码有多糟糕,它都无法接近您当前的解决方案。随着时间的推移,您可以比现有的更轻松地改变和改进这些解决方案。

答案 3 :(得分:0)

看起来您希望两个对象共享其引用计数。你可以通过让第三个对象(TPair)处理引用计数来做到这一点。一个很好的方法是使用implements关键字。您可以选择隐藏第三个对象,也可以与之交互。

使用下面的代码,您可以创建TPairChildATPairChildB或他们的父母&#39; TPair。其中任何一个都会在需要时创建其他对象,并且所有创建的对象都将保持活动状态,直到不再引用它们为止。您当然可以将IObject1之类的接口添加到对象中,但为了简单起见,我将它们保留了下来。

unit ObjectPair;

interface

type
  TPairChildA = class;
  TPairChildB = class;

  TPair = class( TInterfacedObject )
  protected
    FChildA : TPairChildA;
    FChildB : TPairChildB;

    function GetChildA : TPairChildA;
    function GetChildB : TPairChildB;
  public
    destructor Destroy; override;

    property ChildA : TPairChildA read GetChildA;
    property ChildB : TPairChildB read GetChildB;
  end;

  TPairChild = class( TObject , IInterface )
  protected
    FPair : TPair;

    property Pair : TPair read FPair implements IInterface;
  public
    constructor Create( APair : TPair = nil ); virtual;
  end;

  TPairChildA = class( TPairChild )
  protected
    function GetSibling : TPairChildB;
  public
    constructor Create( APair : TPair = nil ); override;

    property Sibling : TPairChildB read GetSibling;
  end;

  TPairChildB = class( TPairChild )
  protected
    function GetSibling : TPairChildA;
  public
    constructor Create( APair : TPair = nil ); override;

    property Sibling : TPairChildA read GetSibling;
  end;

implementation

//==============================================================================
// TPair

destructor TPair.Destroy;
begin
  FChildA.Free;
  FChildB.Free;
  inherited;
end;

function TPair.GetChildA : TPairChildA;
begin
  if FChildA = nil then
    FChildA := TPairChildA.Create( Self );
  Result := FChildA;
end;

function TPair.GetChildB : TPairChildB;
begin
  if FChildB = nil then
    FChildB := TPairChildB.Create( Self );
  Result := FChildB;
end;

// END TPair
//==============================================================================
// TPairChild

constructor TPairChild.Create( APair : TPair = nil );
begin
  if APair = nil then
    FPair := TPair.Create
  else
    FPair := APair;
end;

// END TPairChild
//==============================================================================
// TPairChildA

constructor TPairChildA.Create( APair : TPair = nil );
begin
  inherited;
  FPair.FChildA := Self;
end;

function TPairChildA.GetSibling : TPairChildB;
begin
  Result := FPair.ChildB;
end;

// END TPairChildA
//==============================================================================
// TPairChildB

constructor TPairChildB.Create( APair : TPair = nil );
begin
  inherited;
  FPair.FChildB := Self;
end;

function TPairChildB.GetSibling : TPairChildA;
begin
  Result := FPair.ChildA;
end;

// END TPairChildB
//==============================================================================

end.

用法示例:

procedure TForm1.Button1Click( Sender : TObject );
var
  objA : TPairChildA;
  ifA , ifB : IInterface;
begin
  objA := TPairChildA.Create;
  ifA := objA;
  ifB := objA.Sibling;
  ifA := nil;
  ifB := nil; // This frees all three objects.
end;