【问题标题】:Why should I use Free and not FreeAndNil in a destructor?为什么我应该在析构函数中使用 Free 而不是 FreeAndNil?
【发布时间】:2010-08-18 08:22:05
【问题描述】:

我已阅读A case against FreeAndNil,但仍然不明白为什么我不能在类析构函数中使用此方法?谁能解释一下。

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

【问题讨论】:

  • 你在这里问了一个很好的问题。

标签: delphi destructor delphi-2007


【解决方案1】:

问题是很多 似乎使用 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 而不是 FreeAndNil 来销毁 PropertyA,那么上面的代码甚至可以毫无问题地运行。

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

所以虽然我同意它可以隐藏真正的设计缺陷并且个人不在析构函数中使用 FreeAndNil 的论点,但发现这样的设计缺陷并不是什么灵丹妙药。

【讨论】:

  • 不使用 FreeAndNIL 忽略的问题是,如果你的析构函数遇到异常,留下一个未销毁的对象,可以想象它可能会受到另一次销毁尝试。在这种情况下,已经释放的对象当然不应该再次释放。 FreeAndNIL 防止不完整的析构函数。有一种观点认为,不完整的析构函数也反映了糟糕的设计,但遗憾的是,您并不总是能够准确控制析构函数中将发生的事情(例如,如果您破坏了其他对象,其析构函数行为您无法直接控制或更改)
  • 为了保护,你可以使用类似'FreeAndInvalidate' (delphitools.info/2010/02/06/dont-abuse-freeandnil-anymore) 来使引用无效,而不仅仅是nil,这样所有进一步尝试访问该对象都会触发异常。跨度>
  • 感谢您的链接。这给了我一些见解。
【解决方案2】:

这个问题很容易解释,围绕这个问题的争论比客观更主观。如果对被释放对象的变量引用超出范围,则根本不需要使用 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;

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

在对象析构函数中是否使用.FreeFreeAndNil()的情况是一个灰色地带,但在大多数情况下,.Free应该是安全的,并且在析构函数中取消对子对象的引用应该是不必要。关于如何处理构造函数和析构函数中的异常存在各种争论。

现在请注意:如果您更愿意根据上面概述的具体情况来选择使用.FreeFreeAndNil(),那很好,但请注意,由于而不是 取消随后访问的已释放对象引用可能非常高。如果随后访问指针(对象已释放但引用未设置为 nil),则可能会发生不幸的情况,并且在访问已释放但未取消的对象引用之外的许多代码行中都可能会检测到内存损坏。这种错误可能需要很长时间才能修复,是的,我知道如何使用 FastMM。

因此,对于包括我在内的一些人来说,在所有对象指针被释放时简单地将其置空已经成为一种习惯(也许是一种懒惰的人),即使在没有严格必要的情况下也是如此。

【讨论】:

  • 您正在处理一种情况,即您有一个变量超出范围的小过程。不止于此。但是 +1,我同意使用 FreeAndNil。
  • @Altar:我选择了这个例子,因为它提供了最好的情况,可以说“免费”比“FreeAndNil”更好。所有其他情况都不太精确。重申一下:我在销毁对象实例时总是将我的对象引用清零,这样我就不必考虑是否需要这样做。
【解决方案3】:

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

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

所以我转回打电话给Free。当我认为有必要时,我随后将变量显式设置为nil

【讨论】:

    【解决方案4】:

    我找到了一个关于 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;
    

    【讨论】:

      猜你喜欢
      • 2016-05-23
      • 2014-12-24
      • 2021-08-26
      • 2021-10-31
      • 2016-12-16
      • 2013-10-17
      • 2016-12-23
      • 2018-05-10
      • 2014-03-12
      相关资源
      最近更新 更多