gpt4 book ai didi

delphi - Delphi 中处理循环强引用

转载 作者:行者123 更新时间:2023-12-03 15:02:16 25 4
gpt4 key购买 nike

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

这表示我试图以一种使两个对象都保持事件状态直到不再被引用的方式解决问题:(需要 Delphi 10.1,因为我使用 [unsafe] 属性)

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,它将引用计数转发到“ Controller ”(本例中为 TObject1)。

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

  • “TContainedObject(FObj2).Free;”有点味道,但我没有更好的解决方案,因为我需要使用一个接口(interface)来引用 TObject2 (生产代码在这方面包含一些继承)。有什么办法可以清理它吗?
  • 你很容易忘记将两个类之间的所有引用声明为弱和..
  • 随着更多的类开始出现类似的问题:TObject3 被一个类引用并引用另一个类:内存泄漏。我也可以通过让它从 TContainedObject 继承来处理它,但对于遗留代码来说,这可能不是一件容易的事。

我感觉这个解决方案不能普遍应用,并希望有一个可以 - 或者可能是一个答案来描述为什么很难甚至不可能有一个易于使用的 100% 解决方案来管理此类情况。恕我直言,拥有有限数量的对象,一旦没有从其域外引用它们,它们就会互相破坏,而不必仔细考虑该域内的每个引用,这不会太复杂。

最佳答案

不要使用不安全的内容
[unsafe] 不应在普通代码中使用。
如果您不希望编译器在接口(interface)上进行引用计数,那么这对用户来说确实是一种黑客攻击。

使用弱代替
如果由于某种原因您必须有循环引用,则在其中一个引用上使用 [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;

现在不需要在析构函数中做任何特殊的事情,因此可以省略这些。
编译器会跟踪所有弱引用,并在被引用接口(interface)的引用计数为零时将其设置为 nil。
所有这些都是以线程安全的方式完成的。
然而弱引用本身并不会增加引用计数。

何时使用不安全
这与不安全引用相反,不安全引用根本不发生任何跟踪和引用计数。

您可以在单例接口(interface)类型或禁用引用计数的接口(interface)类型上使用[unsafe]引用。
这里无论如何,引用计数都固定为-1,因此调用addref和release是不必要的开销。
添加[unsafe] 可以消除这种愚蠢的开销。
除非您的接口(interface)覆盖 _addref_release,否则不要使用 [unsafe]

柏林前替代方案
柏林之前,NexGen 编译器之外没有 [weak] 属性。
如果您运行的是 Seattle, 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;

这也将确保接口(interface)得到正确处理,但是现在 TChild.FObject1 不会自动被清空。您可以将代码放入 TParent 的析构函数中,以访问其所有子级并通知它们,如所示的代码所示。
如果循环引用中的参与者之一无法通知其弱链接的对应方,那么您将需要设置一些其他机制来消除这些弱引用。

关于delphi - Delphi 中处理循环强引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37119572/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com