为什么FreeAndNil实现在免费之前做Nil?

时间:2014-10-29 06:18:54

标签: delphi free procedure null

如果你看一下FreeAndNil程序的代码,你会看到:

procedure FreeAndNil(var Obj);
var
  Temp: TObject;
begin
  Temp := TObject(Obj);
  Pointer(Obj) := nil;
  Temp.Free;
end;

他们将Nil分配给对象引用并且只有在它被破坏之后才会是什么原因?为什么不反之亦然?

3 个答案:

答案 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);

您完成了。