将对象作为参数传递 - 不能是对象

WeG*_*ars 1 delphi

我有这两个功能:

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;
Run Code Online (Sandbox Code Playgroud)

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

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

但是,此时,立方体为空但不是NIL.调用FormDestroy时,程序会出现"多个空闲内存泄漏"错误.为什么?我已经调用了TDiplay.CubRelease.不应该是NIL吗?我得到的消息表明该对象被正确释放,但它不是NIL.

实现这个的正确方法是什么?


编辑/澄清

无法准确确定多维数据集的所有者,因为显示的父级正在释放显示,同时仍保留多维数据集一段时间.换句话说,在显示器中显示立方体可以是终身操作,或者它可以仅发生一段时间.此外,在某些情况下,我可能根本不显示立方体.

换句话说,当主窗体关闭时,显示器可以存在或不存在.

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

Rob*_*edy 8

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

如果您的显示对象拥有多维数据集,那么其他任何人都不应该尝试释放它.根据此代码,调用CubAssign 将所有权转移到显示对象,因为显示对象始终释放多维数据集对象.因此,任何调用的代码都CubAssign必须记住永远不要试图释放对象本身.

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

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

CubAssign(Cub, ReleaseCubOnExit);
IOwnCub := not ReleaseCubOnExit;
Run Code Online (Sandbox Code Playgroud)

然后,当你关于释放多维数据集时,检查你是否拥有它:

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Display.CubRelease;
  if IOwnCub then
    Cub.Free;
end;
Run Code Online (Sandbox Code Playgroud)

你声称你不知道CubRelease实际上是否释放了任何东西.实际上你确实如此,因为你在上面展示的实现总是释放对象.我怀疑你打算ReleaseCubOnExit像这样使用这个属性:

procedure TDisplay.CubRelease;                                            
begin
  if ReleaseCubOnExit then
    FCub.Free;
  FCub := nil;
  Clear;   
end;
Run Code Online (Sandbox Code Playgroud)

您拥有的异常捕获代码毫无意义,因为它并没有真正解决导致异常的原因,所以我已将其删除.我还删除了是否FCub为空引用的检查,因为它无关紧要.调用FreeOnNilnull引用总是安全的,所以不要事先检查.它只是使你的代码混乱.该FreeOnNil调用本身是有点毫无意义,也因为你需要的变量是nil不管有什么事,来释放.

一旦您的显示对象尊重该ReleaseCubOnExit属性,您的其他代码也可以使用它.IOwnCub我没有使用前面提到的变量来跟踪所有权,而是可以使用display的属性,如下所示:

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

那么,为什么,当你自由的时候FCub,也Cub不会被设定为nil?因为这不是变量的工作方式.它们是两个独立的变量.事实上,你已经知道了.他们从两个变量开始.一个属于表单类,一个属于显示类.你初始化表单的变量,可能是这样的:

Cub := TCube.Create;
Run Code Online (Sandbox Code Playgroud)

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

var
  x, y: Integer;

x := 4;
y := x;
x := 3;
Assert(y = 4);
Run Code Online (Sandbox Code Playgroud)

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

var
  x, y: TObject;

x := TObject.Create;
y := x;
x := nil;
Assert(y <> nil);
Run Code Online (Sandbox Code Playgroud)

我们改变了价值x,但价值y保持不变.

尽管两个变量引用同一个对象,但它们仍然是两个独立的变量.对象本身独立于引用它的两个变量.也许图表会有所帮助.

Cub
+-----+
|  o----+
+-----+ |
        \    object
         +-->+-------+
FCub    /    |       |
+-----+ |    |       |
|  o----+    +-------+
+-----+

两个变量指的是单个对象.

调用Free变量不会更改变量的值.它只会破坏变量引用的对象.这就是为什么有两个不同的功能,FreeFreeAndNil.后者分配nil给传入的变量.正如我们上面所建到的,为一个变量赋值不会改变任何碰巧具有相同值的其他变量,因此在调用之后FreeAndNil(FCub),上图变为如下所示:

Cub
+-----+
|  o------> ???
+-----+


FCub
+-----+
| nil |
+-----+

Cub我们称之为悬挂参考,因为箭头只是在空间中悬空,而不是指向任何有效的东西.


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

procedure TDisplay.CubAssign(Obj: TCube; Form: TForm1; ReleaseOnExit);
begin
  FCub := Obj;
  FForm := Form;
  FReleaseOnExit := ReleaseOnExit;
end;
Run Code Online (Sandbox Code Playgroud)

然后,当释放多维数据集时,也清除表单上的引用:

FreeAndNil(FCub);
FForm.Cub := nil;
Run Code Online (Sandbox Code Playgroud)

这就产生了所谓的紧耦合 ; 这两个类现在不能彼此分开存在,因为显示形式需要一个TForm1实例.它不适用于任何其他类型的表单,它所拥有的多维数据集必须属于表单.

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

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

  • 此答案中的大量信息以及已投入的工作为+1.这就是我喜欢的东西:) (2认同)