为什么我要在析构函数中使用Free而不是FreeAndNil?

时间:2010-08-18 08:22:06

标签: delphi destructor delphi-2007

我已阅读A case against FreeAndNil但仍不明白为什么我不能在类析构函数中使用此方法?任何人都可以解释。

更新:我认为来自Eric Grange的评论对我来说最有用。链接表明,如何处理它并不明显,这主要是品味问题。 FreeAndInvalidate方法也很有用。

4 个答案:

答案 0 :(得分:14)

  

问题在于很多   似乎使用FreeAndNil作为一些魔术   将杀死那个神秘的子弹   撞龙。如果在中使用FreeAndNil()   析构函数似乎解决了崩溃问题   或其他内存损坏问题,   那么你应该深入挖掘   真正的原因。当我看到这个,   我问的第一个问题是,为什么是   实例字段后访问   那个例子被摧毁了?那   通常指向设计问题。

它认为它隐藏了你所遇到的真正问题。它必须意味着您的代码正在访问已经销毁的对象的属性/字段/方法(调用析构函数)。因此,不要用FreeAndNil隐藏真正的问题,而应该真正解决潜在的问题。

如果您在SomeObject的析构函数中使用FreeAndNil PropertyA,则下面的代码不会崩溃。但它隐藏了SomeObject在被销毁后使用的真正问题。最好解决这个设计问题(访问被破坏的对象)而不是隐藏它。

SomeObject.Free;  // Destructor called
if Assigned(SomeObject.PropertyA) then  // SomeObject is destroyed, but still used
  SomeObject.PropertyA.Method1;  

修改

在另一种情况下,有人可能会争辩说如果不使用FreeAndNil,代码也不会崩溃。因为即使对象被破坏,内存也可能不被重用,并且所有结构都可能是完整的。如果使用Free来销毁PropertyA而不是FreeAndNil,上面的代码甚至可能没有问题。

如果使用FreeAndNil来销毁SomeObject,无论析构函数中的代码是什么,你都会看到真正的问题。

因此,虽然我同意它可以隐藏真正的设计缺陷并且个人不在析构函数中使用FreeAndNil的论点,但发现这样的设计缺陷并不是一个神奇的子弹。

答案 1 :(得分:7)

这个问题很容易解释,围绕这个问题的争论比客观更主观。如果对被释放对象的变量引用超出范围,则无需使用FreeAndNil:

procedure Test;
var
  LObj: TObject;
begin
  LObj := TObject.Create;
  try
    {...Do work...}
  finally
    //The LObj variable is going out of scope here,
    // so don't bother nilling it.  No other code can access LObj.

    //FreeAndNil(LObj);
    LObj.Free;
  end; 
end;

在上面的代码片段中,由于给出的原因,将LObj变量缩小是没有意义的。但是,如果可以在应用程序的生命周期中实例化并释放多次对象变量,则有必要检查该对象是否确实已实例化。检查这个的简单方法是对象引用是否已设置为nil。为了便于将该设置设置为nilFreeAndNil()方法将释放资源,并为您设置nil。然后,在代码中,您可以检查对象是否使用LObj = nilAssigned(LObj)进行实例化。

是否在对象析构函数中使用.FreeFreeAndNil()的情况属于灰色区域,但在大多数情况下,.Free应该是安全的,并且将对析构函数中的对象应该是不必要的。关于如何处理构造函数和析构函数中的异常,有各种各样的论据。

现在请注意:如果您希望选择是否使用.FreeFreeAndNil(),具体取决于上面列出的具体情况,那很好,但请注意由于< em> not nilling随后访问的自由对象引用可能非常高。如果随后访问指针(对象已释放但引用未设置为nil),则可能发生您不幸的情况,并且检测到内存损坏会发生许多代码行,而不是访问已释放但未填充的对象引用。这种bug可能需要很长时间才能解决,是的,我知道如何使用FastMM。

因此,对于包括我在内的某些人来说,即使不是非常必要的,也只是在释放所有对象指针时,习惯(可能是一个懒惰的人)就成了习惯。

答案 2 :(得分:5)

我倾向于经常使用FreeAndNil(无论出于何种原因)但不再使用nil。让我停止这样做的原因与变量是否需要TSomething之后无关。它与代码更改有关,尤其是变量的类型更改。

在将变量类型从ISomething更改为接口类型FreeAndNil后,我被咬了好几次。 Free并没有抱怨,并且继续在接口变量上继续工作。这有时会导致神秘的崩溃,无法立即追溯到它发生的地方,并花了一些时间才能找到。

所以我改回来调用nil。当我认为有必要时,我明确地将变量设置为{{1}}。

答案 3 :(得分:3)

我在讨论有关FreeAndNilFreeAndInvalidate的stackoverflow问题时提到现在的Microsoft安全开发生命周期recommends something similar to FreeAndInvalidate

  

根据上面的SDL建议 - 以及与重新使用已删除C ++对象的陈旧引用相关的一些实际错误...

     

清理价值的明显选择是NULL。但是有一些缺点:我们知道大量的应用程序崩溃是由于NULL指针解引用。选择NULL作为清理值意味着此功能引入的新崩溃可能不太可能让开发人员看起来需要适当的解决方案 - 即正确管理C ++对象生命周期 - 而不仅仅是一个禁止立即症状的NULL检查。

     

同样检查NULL是一个公共代码构造,这意味着对NULL的现有检查与使用NULL作为清理值相结合可能偶然地隐藏了其根本原因确实需要解决的真正的内存安全问题。

     

出于这个原因,我们选择了0x8123作为清理值 - 从操作系统的角度来看,这与零地址(NULL)在同一个内存页面中,但是0x8123处的访问冲突将更好地向开发人员展示需要更详细的关注。

基本上是这样的:

procedure FreeAndInvalidate(var obj);
var
   temp : TObject;
const 
   INVALID_ADDRESS = $8123; //address on same page as zero address (nil)
begin
   //Note: Code is public domain. No attribution required.
   temp := TObject(obj);
   Pointer(obj) := Pointer(INVALID_ADDRESS);
   temp.Free;
end;