7

我有一个关于 OO Delphi 最佳实践的一般性问题。目前,我将 try-finally 块放在我创建对象的任何地方,以便在使用后释放该对象(以避免内存泄漏)。例如:

aObject := TObject.Create;
try
  aOBject.AProcedure();
  ...
finally
  aObject.Free;
end;

代替:

aObject := TObject.Create;
aObject.AProcedure();
..
aObject.Free;

您认为这是一种好的做法,还是过多的开销?那么性能呢?

4

8 回答 8

17

使用 try-finally 绝对是最佳实践。

如果引发异常,该对象被释放。

至于性能:在优化之前进行测量。

于 2010-05-27T16:40:00.260 回答
15

在我看来,对象构造不应跟随(或梅森指出的“in”try / finally块 只有一个原因。

  1. 如果一个对象的生命周期由另一个对象管理。

这种管理可以采取三种形式:

  1. 对象的引用具有超出本地块的范围并在其他地方释放 - 就像在析构函数中释放的字段成员一样。
  2. 立即添加到列表中的对象,该列表负责稍后释放该对象。
  3. 具有关联生命周期管理器的对象,例如您如何将所有者传递给构建时的 VCL 控件。

使用#1当引用具有更广泛的范围时,如果没有立即构造引用,则应立即将其设置为 nil。这样,当检查它以供参考时,您就知道您的读数准确。这对于作为较大类的一部分构造的成员对象最为常见,然后在父对象被销毁时进行清理。

使用#2当将对象添加到列表时,您希望使用try-except块(我使用的少数几次之一),以防在构造对象之后和将其添加到管理列表之前发生异常。理想情况下,构造后的第一行是将其添加到列表中,或者列表实际上是一个工厂类,它为您提供了一个已经添加到列表中的对象。

使用#3当一个对象有另一个生命周期管理器时,您确实应该确保由该管理器管理它是正确的做法。如果您正在构建一个 VCL 控件,您可能很想让表单(或任何其他控件)拥有它,但这实际上会给构建和销毁带来额外的开销。如果可能,您应该显式地释放它,如果您将控件打开一次尤其如此,那么您就会知道您将在表单的析构函数中或在它关闭时释放它。唯一不能这样做的情况是控件创建更加动态。

所以是的,使用大量try / finally块是最佳实践。您应该只有几个try / except块,并且大多数都应该捕获非常特定的异常类型,和/或重新引发异常。如果你有多个try / excepttry / finally,那么你做错了

于 2010-05-27T20:48:19.457 回答
13

正如弗兰克所说,“至于性能:在优化之前进行衡量。” 重复它以强调。

此外,如果您在一个方法中创建一堆对象,则不需要为每个对象使用 try..finally 块。这可能会导致丑陋的缩进混乱。create, try, create, try, create, try, do something, finally, free, finally, free, finally, free. 啊! 相反,您可以在方法的顶部将对象引用设置为nil ,然后全部创建它们,执行一个try 块,并在 finally 部分中将它们全部释放。

这将节省一些开销和性能(尽管您可能永远不会注意到其中的区别),但更重要的是,它将使您的代码更清晰、更易于阅读,同时保持相同的安全级别。

于 2010-05-27T16:50:54.987 回答
5

回答您问题的第二部分:
try finally几乎没有任何开销。

事实上,有很多方法都有隐式的 try...finally 块。例如,仅具有使用任何接口类型的本地 var 的函数并为其分配一个值。

——杰伦

于 2010-05-27T17:07:53.830 回答
5

去年在 Delphi 开发者日,我看到了 Marco Cantu 的一些私人代码,他每次创建任何东西时都会调用 try finally。

有人问他这件事,他说他一直都在努力。

但是当涉及到进入和退出临界区时,这对于多线程代码来说尤其是一个好主意,虽然这不是主题,但记住这是一件好事。

显然,有时它有点突兀,如果你的工作环境文化中不适合你的鲁棒性,它可能会让你看起来像一个两双鞋的好人。但我认为这是个好主意。这有点像 Delphi 对强制手动垃圾收集的尝试。

于 2010-05-27T17:33:28.947 回答
2

如果您在类的构造函数中创建对象并且该对象将由封闭类拥有,您将希望在拥有类的析构函数中释放它。

我倾向于使用 FreeAndNil() 而不是调用 Free。

编辑:但正如其他人所说,你肯定总是想释放你创造的东西。

于 2010-05-27T17:26:35.063 回答
1

是的,如果创建对象的代码负责释放它,那总是一个好主意(必不可少)。如果不是,那么 try/finally 是不合适的,但无论如何 .Free 也不合适!

但是,让这个样板代码填充您的“业务逻辑”可能会很麻烦,您可能需要考虑一种方法,它具有释放对象的相同保证但更清洁(并具有其他好处),例如我的自己的 AutoFree() 实现

使用 AutoFree() 你的代码可以写成:

aObject := TObject.Create;
AutoFree(@aObject);

aObject.AProcedure();

或者,由于实现使用对引用的引用(以启用自动 NIL'ing 和 Free'ing),您甚至可以预先注册您希望 AutoFree'd 的引用以保持此类内务声明远离从您的业务逻辑中,并尽可能保持代码的真正“肉”(这在可能需要释放多个对象时特别有用):

AutoFree(@aObject1);
AutoFree(@aObject2);

aObject1 := TObject.Create;
aObject1.AProcedure();

// Later on aObject2 is (or may be) also created
 :

在我的原始帖子中未显示的是支持在单个 AutoFree() 调用中注册多个引用的机制的后续添加,但我相信您可以自己找出支持此功能所需的更改,如果您愿意的话能够做到这一点:

AutoFree([@aObject1, @aObject2]);

aObject1 := TObject.Create;
aObject1.AProcedure();

// Later on aObject2 is (or may be) also created
 :
于 2010-05-27T21:26:45.480 回答
-4

即使非常推荐这样做,我也不会总是这样做。

我使用或不使用 try/finally 的规则:

  • 该物体坠毁和燃烧的机会。
  • 创建对象的次数。如果我知道您的对象很少被创建(在应用程序生命周期中不超过 5-6 次),我可以忍受 20KB 的内存泄漏——以防它在没有被释放的情况下“死亡”。
  • 对象崩溃时泄漏的内存量。
  • 代码复杂度。Try/except 使代码看起来非常难看。如果有 5 行程序,我总是使用 try/except。
  • 应用程序文件跨度。如果您的应用程序需要运行数天,那么我绝对想释放任何内存,否则泄漏会累积。

唯一难以做出决定的地方是当您在创建对象数千次时需要性能(例如在循环中)。在这种情况下,我不使用 try/except 如果对象正在执行简单的任务并且有很小的机会看到它崩溃。

于 2010-05-27T20:33:37.533 回答