【问题标题】:Delphi: why doesn't FreeAndNil *really* nil my object?Delphi:为什么 FreeAndNil *真的* nil 我的对象?
【发布时间】:2011-12-23 14:19:47
【问题描述】:

我想将对象 A 传递给第二个对象 B,让 B 进行一些处理,最后释放 A,以防不再需要它。下面给出了一个淡化的版本。

program Project6;
{$APPTYPE CONSOLE}
uses
  SysUtils;
type
  TMyObject = class(TObject)
  public
    FField1:  string;
    FField2:  string;
  end;
  TBigObject = class(TObject)
  public
    FMyObject:  TMyObject;
    procedure Bind(var MyObject:  TMyObject);
    procedure Free();
  end;
procedure TBigObject.Bind(var MyObject: TMyObject);
begin
  FMyObject := MyObject;
end;
procedure TBigObject.Free;
begin
  FreeAndNil(FMyObject);
  Destroy();
end;
var
  MyObject:   TMyObject;
  BigObject:  TBigObject;
begin
  try
    MyObject := TMyObject.Create();
    BigObject := TBigObject.Create();
    BigObject.Bind(MyObject);
    BigObject.Free();
    if (Assigned(MyObject)) then begin
      WriteLn('Set MyObject free!');
      MyObject.Free();
    end;
    ReadLn;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

(别介意糟糕的设计。)现在,我不明白为什么 FreeAndNil 实际上会释放 MyObject,而 Assigned(MyObject) 被评估为 true(在 MyObject.Free() 处给出 AV)。

有人可以帮我解惑吗?

【问题讨论】:

  • 你应该重命名你的方法Free!在TObject 中已经声明了这样的方法。覆盖Destroy 并在里面调用inherited 会好得多。
  • @Uwe:是的,这是一个糟糕的、糟糕的名字选择。 :) 不过,由于您和其他人的众多评论/回答,我宁愿让它保持原样。谢谢!
  • 简单回答:对象不能被 niled,只有对对象的引用可以:)
  • 更简单的代码示例; A := B; B := nil; 注意,现在只有 B 是 nil,不一定是 A。

标签: delphi memory-management


【解决方案1】:

您的代码有一些特殊之处。

首先,你不应该重写 Free,你应该重写你的类的虚拟析构函数(Destroy)。

但是 ISTM 认为 BigObject 不是 MyObject 的所有者,所以 BigObject 根本不应该尝试释放它。

正如 CodeInChaos 已经说过的,FreeAndNil 只释放 一个 变量,在本例中是 FMyObject 字段。无论如何都不需要 FreeAndNil,因为在对象被释放后不会发生任何事情。

Assigned 不能用于检查对象是否已被释放。它只能检查 nil,而 FreeAndNil 只将 一个引用 设置为 nil,而不是对象本身(这是不可能的)。

你的程序设计应该是这样的,一个对象只有在没有人访问它的情况下才能并且只会被释放。

【讨论】:

  • 感谢 Rudy 分享一些关于设计的深刻见解。
【解决方案2】:

您有两个对该对象的引用副本,但仅将其中一个设置为 nil。你的代码相当于这个:

i := 1;
j := i;
i := 0;
Writeln(j);//outputs 1

我在此示例中使用整数,因为我确信您熟悉它们的工作方式。对象引用实际上只是指针,其行为方式完全相同。

根据对象引用转换示例使其看起来像这样:

obj1 := TObject.Create;
obj2 := obj1;
obj1.Free;//these two lines are
obj1 := nil;//equivalent to FreeAndNil
//but obj2 still refers to the destroyed object

旁白:你永远不应该直接调用 Destroy 并且永远不要声明一个名为 Free 的方法。而是重写 Destroy 并调用 TObject 中定义的静态 Free,或者实际上是 FreeAndNil。

【讨论】:

  • 感谢您的回答以及您对 Free and Destroy 使用的建议。
  • @David 这两行并不完全等同于FreeAndNil,因为FreeAndNil 先将引用取零,然后再销毁对象。不过,在大多数情况下,这并不重要。 +1 很好的解释
  • @smasher 是的,这是一个公平的观点,但我希望您能原谅我,以帮助您顺利进行阐述!
  • @DavidHeffernan:很有学校风格的解释……非常喜欢!! +1
【解决方案3】:

MyObject 是与字段 FMyObject 不同的变量。而你只是niling 字段FMyObject

FreeAndNil 释放指向的对象,nils 是你传入的变量。它不会神奇地发现和nil 指向你释放的对象的所有其他变量。

FreeAndNil(FMyObject); 做同样的事情:

object(FMyObject).Free();
FMyObject=nil;

(从技术上讲,这并不完全正确,由于var 参数未指定类型,转换为对象是重新解释转换,但这与此处无关)

这显然只修改FMyObject 而不是MyObject


哦,我刚刚注意到你隐藏了原来的 Free 方法?这太疯狂了。 FreeAndNil 仍然使用原来的 Free。在您的示例中这并没有影响到您,因为您在静态类型为TBigObject 而不是FreeAndNil 的变量上调用Free。但这是灾难的收据。

您应该改写析构函数Destroy

【讨论】:

  • @CIC:感谢您的解释(+1)。不过,我会相信 TOndrej,因为他是第一个。
  • @conciliator 事实上 CodeInChaos 是第一个,但你应该接受最好的而不是第一个,无论你认为是最好的。
  • @David:看来我的阅读时间与你们不同。你的,CIC 和 TOndrejs 的回答都让我满意,所以我会接受第一个正确的答案。
  • @conciliator 不,我们的时间都一样。 CIC是第一个。我认为 CIC 的答案是最好的,这是我会接受的。
【解决方案4】:

原因很简单,你没有一个引用而不是另一个。考虑这个例子:

var
  Obj1, Obj2: TObject;
begin
  Obj1 := TObject.Create;
  Obj2 := Obj1;
  FreeAndNil(Obj1);
  // Obj1 is released and nil, Obj2 is non-nil but now points to undefined memory
  // ie. accessing it will cause access violations
end;

【讨论】:

  • 感谢您的快速(和正确!)回复,伙计。事后看来,很明显,我感到很尴尬......截止日期可以让你(至少我)过早地发布,似乎。 :)
  • 顺便说一句,公平地说,我不是第一个,@CodeInChaos 在我面前发布了他的答案。
  • 我通常会说,尤其是对那些管理内存有问题的人:尽可能避免使用 FreeAndNil。它通常不是必需的,而且它通常不是您想要它做的事情。好的代码可以不用 FreeAndNil。
  • @rudy 这通常是个坏建议。使用 FreeAndNil 可以更轻松地找到错误。不使用它不会。
  • 啊,旧的FreeAndNil vs TObject.Free 讨论...这东西可以永远存在 ehehehehe :-)
猜你喜欢
  • 2012-05-19
  • 2014-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-10
  • 2011-09-24
  • 1970-01-01
  • 2011-04-24
相关资源
最近更新 更多