我有这两个功能:
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就像多维数据集的存储(所有者)一样.
实现这一点的正确方法是始终清楚谁负责每个对象.谁拥有立方体?
如果您的显示对象拥有多维数据集,那么其他任何人都不应该尝试释放它.根据此代码,调用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变量不会更改变量的值.它只会破坏变量引用的对象.这就是为什么有两个不同的功能,Free和FreeAndNil.后者分配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只要不调用销毁方法,表单就可以使用变量.这种向感兴趣方通知的技术被称为观察者模式.