我有两个类(在我的示例中为 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 超出范围,所有实例都会被销毁。该解决方案基于将引用计数转发给“控制器”(在本例中为 TObject1)的 TContainedObject。
现在我知道这个解决方案有缺陷,这就是我的问题开始的地方:
- “TContainedObject(FObj2).Free;” 有点味道,但我没有更好的解决方案,因为我需要使用一个接口来引用 TObject2(生产代码为此包含一些继承)。有什么想法可以清理吗?
- 您很容易忘记将 2 个类之间的所有引用声明为弱和 ..
- 类似的问题随着更多的类而开始出现:让 TObject3 被一个引用并引用另一个:内存泄漏。我也可以通过让它从 TContainedObject 下降来处理它,但是对于遗留代码,这可能不是一件容易的事。
我觉得这个解决方案不能被普遍应用,并希望有一个可以 - 或者可能是一个答案来描述为什么很难甚至不可能有一个易于使用的 100% 解决方案来管理这种情况。恕我直言,拥有有限数量的对象并不会太复杂,一旦它们没有从其域外引用,它们就会相互破坏,而不必仔细考虑该域内的每个引用。