Dal*_*kar 8 delphi automatic-ref-counting
我有两个引用计数的类,它们相互引用。这些参考之一被标记为[weak]防止创建强参考周期。
type
TFoo = class(TInterfacedObject)
private
[weak]
FRef: IInterface;
public
constructor Create(const ARef: IInterface);
end;
TBar = class(TInterfacedObject)
private
FFoo: IInterface;
public
constructor Create; virtual;
destructor Destroy; override;
procedure AfterConstruction; override;
end;
constructor TFoo.Create(const ARef: IInterface);
begin
inherited Create;
FRef := ARef;
end;
constructor TBar.Create;
begin
inherited;
end;
destructor TBar.Destroy;
begin
inherited;
end;
procedure TBar.AfterConstruction;
begin
inherited;
FFoo := TFoo.Create(Self);
end;
procedure Test;
var
Intf: IInterface;
begin
Intf := TBar.Create;
writeln(Assigned(Intf)); // TRUE as expected
end; // AV here
Run Code Online (Sandbox Code Playgroud)
但是我无法成功完成TBar对象实例的构造,退出测试过程会在处触发Access Violation异常_IntfClear。
带有消息“在0x0040e398发生访问冲突:读取地址0x00000009”的异常类$ C0000005。
单步调试器显示TBar.Destroy在代码到达writeln(Assigned(Intf))行之前被调用,并且在构造过程中没有异常。
为什么在这里构造对象期间调用析构函数,为什么没有例外?
为了了解这里发生的事情,我们需要简短地概述一下Delphi ARC如何在经典编译器下对引用计数对象实例(实现某些接口的对象实例)进行工作。
引用计数基本上是对对象实例的强引用进行计数,并且当对对象的最后一个强引用超出范围时,引用计数将降至0,实例将被销毁。
强引用在这里代表接口引用(对象引用和指针不会触发引用计数机制)和编译器插入通话_AddRef和_Release递增和递减引用计数方法,在适当的地方。例如,当分配给interface _AddRef时,以及该引用超出作用域时,都会调用它_Release。
简化后的方法通常如下所示:
function TInterfacedObject._AddRef: Integer;
begin
Result := AtomicIncrement(FRefCount);
end;
function TInterfacedObject._Release: Integer;
begin
Result := AtomicDecrement(FRefCount);
if Result = 0 then
Destroy;
end;
Run Code Online (Sandbox Code Playgroud)
引用计数对象实例的构造如下:
建筑- TInterfacedObject.Create -> RefCount = 0
NewInstance AfterConstruction链分配给初始强参考 Intf := ...
_AddRef -> RefCount = 1要了解实际的问题,我们需要在施工顺序深入挖掘,特别是NewInstance和AfterConstruction方法
class function TInterfacedObject.NewInstance: TObject;
begin
Result := inherited NewInstance;
TInterfacedObject(Result).FRefCount := 1;
end;
procedure TInterfacedObject.AfterConstruction;
begin
AtomicDecrement(FRefCount);
end;
Run Code Online (Sandbox Code Playgroud)
NewInstance设置为1而不是0?初始引用计数必须设置为1,因为构造函数中的代码可能很复杂,并且可能触发瞬态引用计数,这可能会在构造过程中自动破坏对象,然后才有机会将其分配给使它保持生命的初始强引用。
然后减少该初始参考计数,AfterConstruction并正确设置对象实例参考计数以进行进一步的参考计数。
该问题代码中的真正问题实际上是它在调用后触发AfterConstruction方法中的瞬态引用计数,从而将初始对象引用计数减小回0。因此,对象的计数将增加,然后减小为0,并会自毁打电话。 inheritedDestroy
虽然对象实例在构造函数链中受到保护而不会自我破坏,但在很短的时间内它会处于AfterConstruction方法内部的脆弱状态,我们需要确保在此期间没有可触发引用计数机制的代码。
在这种情况下,触发引用计数的实际代码隐藏在相当意外的位置,并且以[weak]属性的形式出现。因此,应该阻止实例参与引用计数机制的实际上是触发了它-这是[weak]报告为RSP-20406的属性设计中的缺陷。
AfterConstruction移到构造函数中inherited在AfterConstruction方法的末尾而不是开始处调用。AtomicIncrement(FRefCount)在的开头和AtomicDecrement(FRefCount)结尾进行调用来自行执行一些显式引用计数AfterConstruction(您无法使用,_Release因为它将破坏对象)[weak]属性替换为[unsafe](仅在TFoo实例生存期永远不会超过TBar实例生存期的情况下才能执行