【问题标题】:Passing objects as parameters - Cannot NIL an object将对象作为参数传递 - 不能 NIL 对象
【发布时间】:2010-12-17 05:11:39
【问题描述】:

我有这两个功能:

procedure TDisplay.CubAssign(VAR Obj: TCubObj; CONST bReleaseOnExit: boolean);
begin
 ReleaseCubOnExit:= bReleaseOnExit;                             
 FCub:= Obj;    
 if CubReady
 then
  begin
   Init;
   SetScrollBar;
  end
 else Clear;
end;

procedure TDisplay.CubRelease;                                            
begin
 if FCub<> NIL
 then
  TRY
   FreeAndNil(FCub);
  EXCEPT
   MesajErrDetail('CubRelease', 'Cannot free object');
  END
 else FCub:= NIL;            
 Clear;   
end;

我将我的 TDisplay 放在一个表单上,然后通过 CubAssign 创建一个 Cube 对象并将其分配给 TDisplay。后来我通过调用 TDisplay.CubRelease 释放了那个 Cube。现在,当我关闭表单时,我不知道我的 Cube 是否被释放,所以我检查它,如果不是 NIL,我释放它:

procedure TForm1.FormDestroy(Sender: TObject);
begin
 Display.CubRelease;
 if Cub<> NIL
 then FreeAndNil(Cub);
end;

但是,此时 Cube 是空的,但不是 NIL。当调用 FormDestroy 时,程序会给出“Multi Free memory leak”错误。为什么?我已经调用了 TDiplay.CubRelease。不应该是零吗?我收到的消息表明该对象已正确释放,但它不是 NIL。

什么是正确的实现方式?


编辑/澄清

无法准确确定 Cube 的所有者,因为 Display 的父级在释放 Display 的同时仍保留 Cube 一段时间。换言之,在显示器中显示立方体可能是终身操作,也可能只发生一段时间。此外,在某些情况下,我可能根本不显示立方体。

换句话说,当主窗体关闭时,显示可能存在也可能不存在。

在其他实现中(一个简单的查看器,我只想显示立方体),我想让 Display 处理立方体的销毁,因为我不想保留额外的对象列表存储多维数据集。在这种情况下,基本上,显示器就像立方体的存储(所有者)一样。

【问题讨论】:

  • 与 fCub 相比,Cub 是什么?
  • Cub 显然是表单的字段或属性,而FCub 是显示对象的字段。我个人永远不会将 cube 缩写为 cub。柏拉图式固体不应与小熊混淆。
  • “与 fCub 相比,Cub 是什么?” ----> 变量持有相同类型的对象。 Cub 在 TForm1 中声明和创建。 FCub 已声明但未在 TDisplay 中创建。在 CubAssign 中,我分配 FCub:= Cub,所以它们本质上是一回事
  • 不,Altar,它们不是一回事。显然,这是您问题的关键。
  • 对不起,我把这个放在了不好的地方。我绝对知道它们是 2 个不同的变量(因为我分别声明了它们)。我想说它们指向同一个对象——更准确地说是同一个内存地址。

标签: delphi


【解决方案1】:

实现这一点的正确方法是始终清楚谁负责每个对象。谁拥有立方体?

如果您的显示对象拥有立方体,那么任何其他人都不应该尝试释放它。根据这段代码,调用CubAssign将所有权转移给显示对象,因为显示对象总是释放立方体对象。因此,任何调用CubAssign 的代码都必须记住永远不要尝试释放对象本身。

一种方法是将nil 分配给原始多维数据集引用。这样一来,调用者就不会想释放对象,因为它无论如何都不会引用它。

另一种方法是在某处设置布尔值。当代码调用CubAssign 时,它应该随后将False 分配给相关的布尔值,如下所示:

CubAssign(Cub, ReleaseCubOnExit);
IOwnCub := not ReleaseCubOnExit;

然后,当您要免费使用立方体时,请先检查您是否拥有它:

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Display.CubRelease;
  if IOwnCub then
    Cub.Free;
end;

你声称你不知道CubRelease 是否真的释放了任何东西。不过,事实上您确实这样做了,因为您在上面显示的实现总是 释放了对象。我怀疑您打算像这样使用ReleaseCubOnExit 属性:

procedure TDisplay.CubRelease;                                            
begin
  if ReleaseCubOnExit then
    FCub.Free;
  FCub := nil;
  Clear;   
end;

您拥有的异常捕获代码毫无意义,因为它并不能真正解决导致异常的原因,因此我已将其删除。我还删除了对 FCub 是否为空引用的检查,因为这无关紧要。在 null 引用上调用 FreeOnNil 总是安全的,所以不要费心事前检查。它只会使您的代码混乱。 FreeOnNil 调用本身也有点毫无意义,因为您需要将变量设为 nil,而不管是否有任何东西可以释放。

一旦您的显示对象符合ReleaseCubOnExit 属性,您的其他代码也可以使用它。您可以使用 display 的属性,而不是使用我之前提到的 IOwnCub 变量来跟踪所有权,如下所示:

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Display.CubRelease;
  if not Display.ReleaseCubOnExit then
    Cub.Free;
  Cub := nil;
end;

那么,为什么当您释放 FCub 时,Cub 不也设置为 nil?因为这不是变量的工作方式。它们是两个独立的变量。事实上,你已经知道这一点。他们以两个变量开始生活。一个属于表单类,一个属于展示类。你初始化表单的变量,大概是这样的:

Cub := TCube.Create;

这是否也设置了显示对象的FCub 变量的值?不,当然不。为什么要呢?要让FCub 获得一个值,您需要稍后在CubAssign 方法中分配它。它们是两个独立的变量。您对其中一个的值所做的更改不会影响另一个的值。也许对象部分让你感到困惑。您知道单独的整数变量不会相互影响,对吧?

var
  x, y: Integer;

x := 4;
y := x;
x := 3;
Assert(y = 4);

虽然我们使用x 分配了y,但我们可以对x 进行进一步更改而不影响y。断言通过是因为 y 继续保留其先前的值 4。对象引用类型的变量也是如此:

var
  x, y: TObject;

x := TObject.Create;
y := x;
x := nil;
Assert(y <> nil);

我们更改了x 的值,但y 的值保持不变。

即使两个变量引用同一个对象,它们仍然是两个独立的变量。对象本身独立于引用它的两个变量而存在。也许图表会有所帮助。

幼兽 +-----+ | o----+ +-----+ | \ 目的 +-->+-------+ FCub / | | +-----+ | | | | o----+ +--------+ +-----+

引用单个对象的两个变量。

对变量调用Free 不会更改变量的值。它只会破坏变量引用的对象。这就是为什么有两个不同的函数,FreeFreeAndNil。后者将nil分配给传入的变量。正如我们在上面建立的,为一个变量分配一个值不会改变任何其他恰好具有相同值的变量,所以在你调用FreeAndNil(FCub)之后,上图更改为如下所示:

幼兽 +-----+ | o-----> ??? +-----+ FCub +-----+ |无 | +-----+

Cub 就是我们所说的悬空引用,因为箭头只是悬空在空间中,不再指向任何有效的东西。


那么,你如何解决这个问题?显示对象不知道对立方体对象的其他引用。您可以在为显示提供对多维数据集的引用的同时为显示提供对表单的引用:

procedure TDisplay.CubAssign(Obj: TCube; Form: TForm1; ReleaseOnExit);
begin
  FCub := Obj;
  FForm := Form;
  FReleaseOnExit := ReleaseOnExit;
end;

然后,当立方体被释放时,也清除表单上的引用:

FreeAndNil(FCub);
FForm.Cub := nil;

这就产生了所谓的紧耦合;这两个类现在不能彼此分开存在,因为显示表单需要一个TForm1 实例。它不适用于任何其他类型的表单,并且它所持有的立方体必须属于该表单。

紧耦合通常是个坏主意。它会解决这个特定的问题,所以它现在对你来说可能看起来不错,但它最终会扼杀你的程序的开发,因为你将无法重用任何东西。这个答案的第一句话给出了一个更好的解决悬空引用问题的方法。如果多维数据集对象可以在表单不知情的情况下随时销毁,则表单不应再使用其Cub 变量,因为它不拥有它所引用的对象。 p>

您可以通过给立方体一个列表来缓解这个问题,它可以在列表中跟踪任何有兴趣了解它的破坏的人。该列表可以是TNotifyEvent 方法指针之一。当立方体对象被销毁时,它可以遍历列表并调用每个方法指针。表单对象之前已经向多维数据集对象注册了一个方法。当该方法被调用时,form 可以清除它自己对多维数据集的引用。这样,立方体不需要知道它的所有引用在哪里,显示器也不需要知道。只要不调用销毁方法,表单就可以使用Cub 变量。这种通知相关方的技术被称为观察者模式

【讨论】:

  • 嗨,罗伯。可能我误导了你,因为我没有提供足够的细节。 CubRelease 用于从 Display 内部手动释放立方体。它不是为了实现 TDisplay.Destroy 的功能!我的 TDisplay.Destroy (此处未显示)完全实现了您在 CubRelease 中实现的内容:“如果 ReleaseCubOnExit 然后免费...” ------ 无论如何,我的问题是为什么多维数据集在 TForm1.FormDestroy 中不是 NIL (请参阅我的代码)? ReleaseCub 正在(成功地)调用 FreeAndNil(FCub)。 FCub 指向 Cub(在 Form1 中创建),那么为什么 FCub 是 NIL 而 Cub 不是 NIL?我怎样才能做到这一点。坦克
  • 更准确地说,将对象作为参数传递给函数,会在函数内部创建一个指向原始对象的新指针?如果是,我明白为什么只有 FCub 是 NIL。我如何将两个指针都归零(当然,从 TDisplay 内部)?
  • 嗨,罗伯。这就解决了这个问题。非常感激。非常感谢。
  • +1 表示此答案中的大量信息以及已投入其中的工作。这就是我喜欢 SO :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-05-17
  • 2020-06-05
  • 1970-01-01
  • 1970-01-01
  • 2018-04-17
  • 1970-01-01
  • 2016-08-28
相关资源
最近更新 更多