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

men*_*raz 18 delphi weak-references

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

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

Mar*_*ema 36

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

一个弱的参考被用来打破"保持彼此活着"的拥抱.这是通过将一个引用声明为绕过引用计数机制的纯指针来完成的.

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;
Run Code Online (Sandbox Code Playgroud)

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

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

ISomething = Interface(IInterface)
end;

TSomething = class(TInterfacedObject, ISomething)
end;

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

TChild = class(TSomething)
  FParent: ISomething;
end;
Run Code Online (Sandbox Code Playgroud)

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

这可以用以下方法解决weak reference:

TChild = class(TSomething)
  FParent: Pointer;
end;
Run Code Online (Sandbox Code Playgroud)

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

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


Arn*_*hez 10

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

  • 弱引用pointerclass实例;
  • 低级值类型的显式副本,如integer, Int64, currency, doublerecord(和旧的已弃用objectshortstring);
  • 写入复制,用于高级值类型的引用计数(例如,动态数组);string, widestring, variant
  • interface实例引用计数的强引用 ;

强引用计数的主要问题是潜在的循环引用问题.当a 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;
Run Code Online (Sandbox Code Playgroud)

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

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

procedure TChild.SetParent(const Value: IParent);
begin
  FParent := Value;
end;
Run Code Online (Sandbox Code Playgroud)

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

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

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

procedure SetWeak(aInterfaceField: PIInterface; const aValue: IInterface);
begin
  PPointer(aInterfaceField)^ := Pointer(aValue);
end;
Run Code Online (Sandbox Code Playgroud)

因此,它可以这样使用:

procedure TParent.SetChild(const Value: IChild);
begin
  SetWeak(@FChild,Value);
end;

procedure TChild.SetParent(const Value: IParent);
begin
  SetWeak(@FParent,Value);
end;
Run Code Online (Sandbox Code Playgroud)

您可以尝试阅读我关于Delphi中弱引用的博客文章 - 及其相关的源代码:我们已经实现了直接的弱引用,并且将从Delphi 6到XE2的弱引用接口处理"归零".

实际上,在某些情况下,nil如果在子节点之前释放引用实例,则需要将接口弱字段设置为,以避免任何访问冲突问题.这被称为" 归零弱指针 ",以及Apple使用ARC模型实现的,以及我们尝试在Delphi中实现的.


mjn*_*mjn 5

也可以看看

Delphi移动编译器中的自动引用计数

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

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