“弱参考”:需要脚踏实地的解释

时间:2012-07-16 09:00:31

标签: delphi weak-references

有人可以在Delphi中提供弱引用的解释吗?

我注意到在我仔细检查的一些库/框架源代码中经常提到这个概念。我陷入了困境,希望对它有一个明确的理解。

4 个答案:

答案 0 :(得分:35)

通过接口引用相互引用的实例在基于引用计数的接口实现中保持彼此活动。

弱引用用于打破"让彼此保持活力"熊抱。这是通过将一个引用声明为绕过引用计数机制的纯指针来完成的。

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;

即使彼得和约翰超出范围,他们的实例仍然存在,因为他们的相互参照使他们的引用不会降到零。

在复合模式(父子关系)中更常见问题,其中子项具有对父项的后引用:

ISomething = Interface(IInterface)
end;

TSomething = class(TInterfacedObject, ISomething)
end;

TParent = class(TSomething)
  FChildren: TInterfacedList;
end;

TChild = class(TSomething)
  FParent: ISomething;
end;

同样,父母和孩子可以互相保持,因为他们的共同参考使他们的引用不会降到零。

这是通过weak reference

解决的
TChild = class(TSomething)
  FParent: Pointer;
end;

通过将FParent声明为"纯粹的"指针引用计数机制不会对父对象的后向引用起作用。当父级超出范围时,其引用计数现在可以降至零,因为其子级不再将其引用计数保持在零以上。

注意此解决方案确实需要特别注意生命周期管理。孩子们可以在父母的生命周期之后保持活力,因为孩子们可以在外面"这些类中有一个对孩子的引用。当孩子假定父引用始终指向有效实例时,这可以导致各种有趣的AV。如果你需要它,请确保当父项超出范围时,它会使子项在其自己对子项的引用之前没有它们的后引用。

答案 1 :(得分:10)

默认情况下,Delphi中的所有引用都是:

  • weak references 代表pointerclass个实例;
  • 显式副本,用于低级值类型,例如integer, Int64, currency, doublerecord(旧的已弃用object或{{1 }});
  • copy-on-write reference counting用于高级值类型(例如shortstring动态数组);
  • 强引用,其中包含string, widestring, variant个实例的引用计数;

强引用计数的主要问题是潜在的循环引用问题。当interface具有对另一个的强引用时,会发生这种情况,但目标interface有一个强指针返回原始状态。即使删除了所有其他引用,它们仍将保持相互之间并且不会被释放。这也可以通过一系列对象间接发生,这些对象可能在链中的最后一个引用回早期对象。

例如,请参阅以下接口定义:

interface

以下实现最终会泄漏内存:

  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;

在Delphi中,最常见的引用复制变量(即变体,动态数组或字符串)通过实现 copy-on-write 解决了这个问题。不幸的是,这种模式不适用于接口,它不是值对象,而是绑定到实现类的引用对象,它们无法复制。

请注意,基于垃圾收集器的语言(如Java或C#)不会遇到此问题,因为循环引用由其内存模型处理:对象生存期由内存管理器全局维护。当然,它会增加内存使用,由于分配和分配期间的其他操作而导致进程变慢(所有对象及其引用必须在内部列表中维护),并且当垃圾收集器进入操作时可能会降低应用程序的速度。

使用没有垃圾收集的语言(如Delphi)的一种常见解决方案是使用弱指针,通过该指针将接口分配给属性而不增加引用计数。为了轻松创建弱指针,可以使用以下函数:

procedure TParent.SetChild(const Value: IChild);
begin
  FChild := Value;
end;

procedure TChild.SetParent(const Value: IParent);
begin
  FParent := Value;
end;

因此,它可以这样使用:

procedure SetWeak(aInterfaceField: PIInterface; const aValue: IInterface);
begin
  PPointer(aInterfaceField)^ := Pointer(aValue);
end;

您可以尝试阅读my blog post about weak references in Delphi - 及其相关的源代码:我们已经实现了直接弱引用,并将从Delphi 6到XE2的弱引用接口处理“归零”。

事实上,在某些情况下,如果您在子节点之前释放引用实例,则需要将接口弱字段设置为procedure TParent.SetChild(const Value: IChild); begin SetWeak(@FChild,Value); end; procedure TChild.SetParent(const Value: IParent); begin SetWeak(@FParent,Value); end; ,以避免任何访问冲突问题。这被称为“归零弱指针”,以及我们尝试在Delphi中实现的Apple implemented with the ARC model

答案 2 :(得分:5)

另见

Automatic Reference Counting in Delphi Mobile Compilers

其中包含新[weak]属性的文档:

  

ARC的另一个重要概念是弱引用的作用,   您可以通过使用[weak]属性标记它们来创建它。

答案 3 :(得分:2)

在最常见的情况下,strong reference控制引用实例的生命周期,而weak reference则不会。术语weak reference可以在垃圾收集器,引用计数接口或公共对象的上下文中使用。

例如,Delphi表单包含对其所有控件的引用;这些引用可以称为强引用,因为当一个表单被销毁时,它的控件也会被销毁。另一方面,Delphi表单的控件引用了它所属的表单。此引用可以调用为弱,因为它不会以任何方式控制表单的生命周期。