Dee*_*031 6 delphi interface reference-counting delphi-10.1-berlin
我有两个类(在我的示例中为TObject1和TObject2),它们通过接口(IObject1,IObject2)相互了解.正如您在Delphi中可能知道的那样,这将导致内存泄漏,因为参考计数器将始终保持在零以上.通常的解决方案是将一个引用声明为弱.这在大多数情况下都适用,因为你通常知道哪一个会被破坏,或者一旦它被销毁就不一定需要弱引用后面的对象.
这说我尝试以这样的方式解决问题,即两个对象都保持活着,直到两者都不再被引用:(因为我使用[unsafe]属性需要Delphi 10.1)
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.
Run Code Online (Sandbox Code Playgroud)
这确实有效..函数Test1和Test2每个创建一个TObject1和TObject2的实例互相引用,一旦o1和o2超出范围,所有实例都会被销毁.该解决方案基于TContainedObject,它将引用计数转发给"控制器"(在本例中为TObject1).
现在我知道这个解决方案存在缺陷,这就是我的问题所在:
我觉得这个解决方案不能普遍应用,并且希望有一个可以 - 或者可能是一个答案,它将描述为什么它很难甚至不可能有一个易于使用的100%解决方案来管理这种情况.Imho,如果没有从域中引用它们而不必仔细考虑该域中的每个引用,那么有一定数量的对象会相互破坏并不复杂.
不要使用不安全
[unsafe]不应该在正常代码中使用。
如果您不希望编译器在接口上进行引用计数,那么这对用户来说确实是一种黑客攻击。
使用弱代替
如果由于某种原因您必须有循环引用,则[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;
Run Code Online (Sandbox Code Playgroud)
现在不需要在析构函数中做任何特殊的事情,因此可以省略这些。
编译器跟踪所有弱引用,并在引用接口的引用计数达到零时将它们设置为 nil。
而这一切都是以线程安全的方式完成的。
然而弱引用本身并不会增加引用计数。
何时使用不安全
这与不安全引用相反,不安全引用根本不发生任何跟踪和引用计数。
[unsafe]您可以在单例接口类型或禁用引用计数的接口类型上使用引用。
这里引用计数在任何情况下都固定为-1,因此调用addref和release是不必要的开销。
消除了[unsafe]这种愚蠢的开销。
除非您的接口覆盖_addref并且_release不使用[unsafe].
Pre Berlin 替代方案Pre Berlin在 NexGen 编译器之外
没有任何属性。
如果您运行的是 Seattle, 2010 或介于两者之间的任何内容,则以下代码将执行{几乎}相同的操作。尽管我不确定这段代码是否会成为多线程代码中竞争条件的受害者。
如果您担心这个问题,请随时举旗,我会进行调查。[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.
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;
Run Code Online (Sandbox Code Playgroud)
这也将确保接口得到正确处理,但现在TChild.FObject1不会自动被清空。您可以将代码放入 的析构函数中TParent以访问其所有子级并通知它们,如所示的代码所示。
如果循环引用中的参与者之一无法通知其弱链接的对应方,那么您将需要设置一些其他机制来消除这些弱引用。
| 归档时间: |
|
| 查看次数: |
736 次 |
| 最近记录: |