在Delphi 5中,Free函数是否会引发异常?

7
在Delphi 5中,我目前编写了一个调用多个变量的Free代码块,例如在finally块中。
...
finally
    a.Free;
    b.Free;
    c.Free;
end;

此代码假定Free永远不会引发异常,因为如果a.Free引发了异常,那么bc的内存将被泄漏。这个假设是否合理?


出于好奇,为什么要踩这个问题?这是一个明确的问题,没有歧义,我真的想知道答案。 耸肩 - Stuart Golodetz
我认为你在这里接受答案的速度太快了。 - NGLN
@NGLN:实际上我同意(抱歉TOndrej),我认为David的回答更好。 - Stuart Golodetz
@StuartGolodetz 没问题,我同意。 - Ondrej Kelle
5个回答

11

Free 方法本身并不会显式地引发异常,但它调用虚析构函数 Destroy,而该函数可能会引发异常。

因此,如果您想确保所有对象都被销毁,即使其中一个析构函数引发异常,您最终会得到像这样的代码:

a := TMyObject.Create;
try
  b := TMyObject.Create;
  try
    ...
  finally
    b.Free;
  end;
finally
  a.Free;
end;

尽管如此,在设计上应该遵循一项原则,即不要在析构函数中引发异常。所以,在我看来,如果在析构函数中引发了异常,那么你的程序基本上已经出了问题。此时泄漏对象并不是什么值得担心的事情。如果您的析构函数引发了异常,那么您可能已经泄漏了,因为该析构函数未能完成。

所以,在我看来,将一些调用Free的操作组合在一起也是完全合理的,并且当然要避免深度嵌套的try/finally语句,这值得努力实现。

如果您只想使用一个try/finally语句,请记住编写以下代码:

a := nil;
b := nil;
try
  a := TMyObject.Create;
  b := TMyObject.Create;
  ...
finally
  b.Free;
  a.Free;
end;

在我的代码库中,我有一些辅助方法可以让这个过程更加简洁。那么代码看起来就像这样:

InitialiseNil(a, b);
try
  a := TMyObject.Create;
  b := TMyObject.Create;
  ...
finally
  FreeAndNil(b, a);
end;

我把我的FreeAndNil函数命名为SysUtils中的同名函数,乍一看可能有点奇怪,但这样做是安全和良性的。当你有两个以上的对象时,这些辅助函数自然而然地发挥作用。


3

取决于析构函数中发生了什么。


完全正确,我删除了我的错误答案。没有考虑到析构函数。这就是为什么析构函数不应该抛出异常的原因。 - jpfollenius

2

导致SomeObj.Free引发异常的原因可能有两个:

  1. SomeObj实例或其祖先类的析构函数中未处理的异常。
  2. 由于未初始化变量SomeObj,导致无效的类引用。

在您的情况下,如果a.Free由于上述任何原因引发异常,则对象bc将会存在内存泄漏,并且可能由于析构函数中未处理的异常而导致对象a中的一些泄漏。


1

如果你的a.free引发了异常,那么a(根据析构函数从a对象字段中释放了多少), b和c对象将会泄漏,因为执行将被中断。无论如何,如果析构函数引发错误,则说明代码存在问题。因此,你应该使用try..finally块来保护代码,但在我看来,你应该验证析构函数在任何情况下都不会出现错误。


2
Stuart的假设是“Free不能引发异常”,但它显然可以。 - Frank Shearar
1
不要忘记,即使是a对象的析构函数抛出异常,它也会泄漏。 - user743382
1
我实际上是指a将会一直泄漏,因为a本身的内存肯定不会被释放,但你指出它的某些子对象可能会被释放是正确的。 - user743382
谢谢大家。我是从C++背景出发的,那里允许在析构函数中抛出异常是非常严重的错误(因为在堆栈被展开时抛出异常会调用std::terminate),所以我做出了(事实上是无效的)假设,认为在Delphi中也是这样的情况。 - Stuart Golodetz
1
@StuartGolodetz 应用程序不会在 Delphi 双重异常时终止,但这并不意味着从析构函数中引发异常是一个好主意。在 Delphi 中也永远不应该这样做。 :) - user743382

0

当然,FREE可能会引发异常 - 因此,如果A.FREE引发异常,则您的代码将泄漏内存,B.FREE和C.FREE将不会被调用。

问题是,您想处理这些异常还是让它们发生?这将取决于您的代码将用于什么,其他开发人员是否将使用它(例如)。为了防止任何内存泄漏,您应该嵌套try..finally部分;

a:=tobject.create;
try
  b:=tobject.create;
  try
    c:=tobject.create;

    ...

  finally
    c.free;
  end;
finally
  b.free;
end;
a.free;

这是一种情况。关键在于您的代码实际上正在做什么,以确定是否应该在try..finally部分中包装A.FREE,尽管我猜您可能应该这样做。


3
你的 a.free; 不在任何 try..finally 块中,如果 b:=tobject.create; 抛出异常(同样适用于 c),你尝试释放 b - user743382
1
此外,如果 c.free 引发异常,你仍然会有一个泄漏,因为你实际上没有释放 c - user743382
这是否是良好的编码风格还有待商榷 - 但我确实说过你应该尝试一下。最后,a.free也要释放;我只是演示了如何做到这一点 :-) - Paul Foster
1
在你的例子中,你应该在 a.free; 周围加上 try..finally。如果你绝对确定代码永远不会引发异常,那么你就不需要它,但是你也不需要包含任何你已经包含的 try..finally - user743382

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接