动态创建/释放运行时窗体的正确方式是什么?

18

我总是注重内存使用来创建我的应用程序,如果你不需要它,那就不要创建它,这是我的想法。

无论如何,以以下内容为例:

Form2:= TForm2.Create(nil);
try
  Form2.ShowModal;
finally
  Form2.FreeOnRelease;
end;

我认为Form2.Destroy可能是更好的选择,这也引出了我的问题...

调用以下两者有何不同:

Form2.Destroy;
Form2.Free;
Form2.FreeOnRelease;

它们都做相同或类似的工作,除非我漏掉了什么。

还有,在什么情况下应该使用上述任何一种?显然,当释放对象时,我理解这一点,但在某些情况下,Destroy是否比Free更适合使用?


3
阅读关于 TObject.FreeTObject.Destroy 的资料。 - Premature Optimization
调用 Free 会检查对象是否为空,然后相应地调用 Destroy。这很安全。调用 Destroy 不会检查涉及的对象是否为 nil,因此可能会触发 VA。 - az01
5个回答

16

Form2:= TForm2.Create(nil);

这段代码是一个“Code-Smell”,因为Form2 可能是全局IDE生成的变量,通常用于保存由IDE创建的TForm2。你可能需要使用一个本地变量,并且有一个更好的变量名。这不一定是错误,只是一个“Code-Smell”(即存在问题的代码风格)。

Form2.Destroy vs Form2.Free

使用Form2.Free,因为它仍会调用Destroy方法。您可以按CTRL+Click单击名称(Free)以查看其实现。实质上,如果Self不为nil,Free方法将调用Destroy方法。

Form2.FreeOnRelease

正如文档所说:"不应直接调用 FreeOnRelease。"


你忘记提到(在我看来)最重要的问题,即“Free”/“Release”的问题。 - Andreas Rejbrand
1
因为对全局变量的警告,我给你点赞。 - Andreas Rejbrand
我以Form2为例,我总是给表单/组件起有意义的名称,例如frmMain、frmAbout、cmdOK、cmdCancel等。从一些评论中看来,最好避免使用FreeOnRelease,而应该使用Free。 - user741875
2
@Craig:重点不在于Form2是一个不具描述性的名称。问题在于,如果你使用try ShowModal finally Free这个方法,就不应该在Unit2的接口部分使用全局变量var Form2: TForm2。相反,在这种情况下,你应该使用一个本地变量。你可以将其命名为Form2 - Andreas Rejbrand
好的,我现在明白了,谢谢Andreas。这样做更有意义。 - user741875

10
我以前从未听说过“FreeOnRelease”。快速谷歌搜索得出了原因。根据官方文档:(链接),“FreeOnRelease”在释放组件实现的接口时调用,被内部使用并调用相应的接口方法。通常不需要直接调用“FreeOnRelease”。
至于“Free”和“Destroy”的区别,“Free”是一项安全功能。基本上实现为“如果self <> nil,则self.Destroy;”。它的创建是为了使构造函数和析构函数安全使用。以下是基本思想:
如果正在构建对象并引发未处理的异常,则会调用析构函数。如果您的对象包含其他对象,则在错误发生之前可能已经创建或尚未创建这些对象,因此您不能仅尝试对所有对象调用“Destroy”。但是您需要一种方法来确保已创建的对象确实被销毁。
由于Delphi在调用构造函数之前清除了对象的地址空间,因此此时尚未创建的任何内容都保证是“nil”。因此,您可以为所有子对象再次编写“if FSubObject <> nil then FSubObject.Destroy”(如果您忘记了,将导致访问冲突),或者您可以使用“Free”方法,该方法会为您执行此操作。(这比C ++要好得多,因为在调用构造函数之前不会清除内存空间,这需要您将所有子对象包装在智能指针中,并使用RAII来维护异常安全性!)

这种方法在其他地方也很有用,没有理由不使用它。我从未注意到Free会带来任何可衡量的性能损失,并且它可以提高代码的安全性,因此建议在所有情况下都使用它。

话虽如此,特别是在处理表单时,还有一个额外的变量需要考虑:Windows消息队列。您不知道是否仍有待处理的消息与您即将释放的表单相关联,因此直接调用Free并不总是安全的。为此,可以使用Release方法。它会向消息队列发送一条消息,导致表单在没有更多消息要处理时自动释放,因此通常是释放不再需要的表单的最佳方式。


6

规范形式为:

Form := TMyForm.Create(nil);
try
  Form.ShowModal;
finally
  Form.Free;
end;

永远不要调用Destroy,而应该始终调用Free

FreeOnRelease是一个完全无关紧要的问题。有时,如果有排队的消息送往您的窗体或其子窗体,则可以选择调用Release,尽管通常这表明设计存在问题。


1
@Andreas 我的回答是最简洁明了的。其他的回答让我感到无聊,但我是这个时代的孩子,无法集中注意力在大量的文字上!;-) - David Heffernan
仍然有用的附加信息。 - user741875
2
有许多情况下释放(Release)是非常合理的,其中最值得注意的是从自身销毁一个窗体(Form)。但是,研究是否需要释放仍然是明智的选择。 - NGLN
NGLN的评论很有帮助。你永远不能在该窗体内(例如按钮单击)调用“Free”;这将导致访问冲突并且只有几秒钟就会发生。但是,如果您需要销毁当前所在的窗体,则可以调用“Release”,并且在安全时期释放它。由于没有人想要在自己的窗体下释放一个窗体 - 最好使用规范形式。 - Ian Boyd
任何仍在队列中的消息,在其目标窗口被销毁后将不会传递到任何地方。无伤大雅。 - Ian Boyd
显示剩余2条评论

5
习惯用法是:
procedure SomeProc;
var
  frm: TForm2;
begin
  frm := TForm2.Create(nil);
  try
    frm.ShowModal;
  finally
    frm.Free;
  end;
end;

或者,除非你不喜欢使用with结构。

with TForm2.Create(nil) do
  try
    ShowModal;
  finally
    Free;
  end;

根据文档,您不应该调用Destroy。实际上,Freeif Self <> nil then Destroy;完全等价。也就是说,它是Destroy的“安全”版本。如果指针恰好为nil,它不会完全崩溃。[要测试这一点,请将一个私有字段FBitmap: TBitmap添加到您的窗体类中,然后在OnCreate(例如)中尝试FBitmap.Free vs. FBitmap.Destroy。]
如果您使用上述方法创建窗体,则Free是完全安全的,除非您在窗体类中进行了一些奇怪的操作。
但是,如果您使用CreateForm(TForm2, Form2)创建窗体并将窗体对象存储在全局实例变量Form2中,并且您不立即释放它[例如,如果您希望窗口在非模态方式下保持在主窗体旁边几分钟],则应该使用Release而不是Free。根据文档

Release不会销毁窗体,直到窗体的所有事件处理程序和窗体上组件的所有事件处理程序都执行完毕。 Release还保证在释放窗体之前处理窗体事件队列中的所有消息。窗体或其子级的任何事件处理程序都应该使用Release而不是Free(Delphi)或delete(C ++)。否则可能会导致内存访问错误。

FreeOnRelease与窗体无关。从文档中可以看出:

通常不需要直接调用FreeOnRelease。


0

另一种方法是在formonclose的Action中传递caFree参数

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end

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