【发布时间】:2016-09-04 07:30:25
【问题描述】:
我有两个类(在我的示例中为 TObject1 和 TObject2),它们通过接口(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,它将引用计数转发给“控制器”(在本例中为 TObject1)。
现在我知道这个解决方案有缺陷,这就是我的问题开始的地方:
- “TContainedObject(FObj2).Free;”有点味道,但我没有更好的解决方案,因为我需要使用一个接口来引用 TObject2(生产代码为此包含一些继承)。有什么办法可以清理吗?
- 您很容易忘记将 2 个类之间的所有引用声明为 weak 和 ..
- 随着类的增多,类似的问题开始出现:TObject3 被一个引用并引用另一个:内存泄漏。我也可以让它从 TContainedObject 下降来处理它,但是对于遗留代码,这可能不是一件容易的事。
我觉得这个解决方案不能被普遍应用,并希望有一个可以 - 或者可能会描述为什么很难甚至不可能有一个易于使用的 100% 解决方案来管理这样的解决方案情况。 恕我直言,拥有有限数量的对象并不会太复杂,一旦它们没有从其域外被引用,它们就会相互破坏,而不必仔细考虑该域内的每个引用。
【问题讨论】:
-
如果对象 1 创建了对象 2,这向我强烈暗示了对象 1 负责并依赖对象 2 的关系。为什么这不是强引用?您提到这不是“真实代码”,我认为真正的问题在于该真实代码中的协作设计,而不是这种枯燥的技术场景。如果您发布了您实际合作的详细信息,也许可以就更合适/可行的方法提出一些建议?
-
您应该几乎总是避免循环引用。这是几乎可以普遍应用的解决方案。你为什么要找一个做坏事的好方法?关于可测试性,然后问为什么需要两个类,其中 A 引用 B,B 引用 A。为什么这是个好主意?如果你可以重构为 A、B 和 C,其中 C 管理 A 和 B,而 A 和 B 都不知道 C。
-
不要使用
[unsafe],而是使用[weak],哦,[unsafe]和[weak]已经在下一代编译器中使用了很长一段时间了。只有 windows 和 osx 编译器在 10.1 中获得了它们。 -
@WarrenP 你可能是对的,在我的例子中,“TObject1”有很多责任,这会导致循环引用。拥有处理这种情况的工具仍然会很好。
标签: delphi interface reference-counting delphi-10.1-berlin