如果你看一下FreeAndNil程序的代码,你会看到:
procedure FreeAndNil(var Obj);
var
Temp: TObject;
begin
Temp := TObject(Obj);
Pointer(Obj) := nil;
Temp.Free;
end;
他们将Nil
分配给对象引用并且只有在它被破坏之后才会是什么原因?为什么不反之亦然?
答案 0 :(得分:11)
我可以想到这样做的两个原因,这两个原因似乎都没有引人注目。
原因1:保证在引发异常时将引用设置为nil
实施实现了这一点。如果析构函数引发,则引用仍设置为nil。另一种方法是使用finally
块:
try
TObject(Obj).Free;
finally
TObject(Obj) := nil;
end;
这样做的缺点是表现。特别是在x86上,try/finally
有点贵。在这样一个基本的例程中,谨慎的做法是避免这种费用。
为什么我会不惜一切代价找到零的愿望?好吧,一旦析构函数开始失败,你也可以放弃。您无法再明确说明您的计划状态。你无法分辨失败的程度以及你的程序处于什么状态。我认为,面对一个提升的析构函数,正确的行动过程是终止进程。
原因2:确保其他线程可以检测到对象被销毁
这又实现了,但没有实际用途。是的,您可以测试是否已分配参考。但那又怎样?另一个线程无法在没有同步的情况下调用对象上的方法。你所能做的就是了解对象是否还活着。如果是这样,那么为什么在析构函数运行之前或之后这个状态发生了变化呢?
因此,虽然我提出这个可能的原因,我无法相信Embarcadero中的任何人都被这个论点所左右。
答案 1 :(得分:10)
David's second reason的变体有点引人注目。虽然有人可能会争辩说,如果它适用,还有其他问题需要解决。
原因3:确保同一线程上的事件处理程序可以检测到对象被销毁
这是一个炮制的假设示例:
TOwner.HandleCallback;
begin
if Assigned(FChild) then
FChild.DoSomething;
end;
TChildClass.Destroy;
begin
if Assigned(FOnCallback) then FOnCallback;
inherited Destroy;
end;
现在,如果TOwner打电话:
FChild.Free;
FChild := nil;
FChild
将在其销毁周期的中间被DoSomething
询问。一个灾难的秘诀。 FreeAndNil
的实施巧妙地避免了这一点。
是的,您可能会争辩说在销毁过程中发射回调事件是危险的,但它确实有其好处。 Delphi / VCL代码中的例子很少。特别是如果你扩大关注范围以包括调用多态方法 - 这也将破坏序列的各个方面置于你的控制之外。
现在我必须指出,我不知道Delphi库代码中可能出现此问题的任何特定情况。但是VCL的某些部分存在一些复杂的循环依赖关系。因此,如果将实现更改为SysUtils中更明显的选择会导致一些令人不快的意外,我不会感到惊讶。
答案 2 :(得分:1)
多年来困扰我的唯一一件事就是FreeAndNil(var obj)
缺乏类型安全性。我知道,由于缺乏足够的语言支持,根本无法正确地做到这一点,但是由于我们拥有泛型已有一段时间了,所以这里有一个快速简单的工具,可以使您的生活更轻松。
type
TypeSafe = class sealed
public
class procedure FreeAndNil<T : class>( var obj : T); static; inline;
end;
class procedure TypeSafe.FreeAndNil<T>( var obj : T);
begin
SysUtils.FreeAndNil( obj);
end;
如果您为常规FreeAndNil(var Obj)
添加了一个重载并将其标记为deprecated
,则您有很大的机会找到有人将接口指针移交给它的所有位置-并且如果代码基地只有足够大,相信我,您会发现有趣的事情。
procedure FreeAndNil(var Obj); inline; deprecated 'use TypeSafe.FreeAndNil<T>() instead';
请注意,由于编译器足够聪明,可以为您找到合适的类型,因此您甚至不必指定<T>
。只需在顶部添加单位并全部更改
FreeAndNil(foobar);
在您的源代码中
TypeSafe.FreeAndNil(foobar);
您完成了。