关于内存管理:Delphi 7 – 何时使用 .create(Application) 以及何时使用 .create(nil)?

Delphi 7 - When to use .create(Application) and when to use .create(nil)?

我最近阅读了很多关于此的内容,但从未找到最终答案。
所以,例如,如果我写:

Form1 := TForm1.Create(Application);

应用程序应该负责从内存中释放表单,对吗?
为什么人们通常会这样做:

1
2
3
Form1 := TForm1.Create(Application);
Form1.ShowModal;
Form1.Free;

??
在某些地方看到,如果您尝试"释放"一个已经被释放的对象,您会收到一个 EAccessviolation 消息,但正如我测试的那样,它并不总是正确的。

所以请,这实际上是如何工作的?
这个 EAccessviolation 事情快把我逼疯了,我怎么能完全理解这个事情??我在哪里可以找到这些宝贵的信息!??


一般规则是:

  • 如果您要自己释放它,请使用 nil 作为所有者。
  • 如果您不打算自己释放它,请指定一个负责释放它的所有者。

所以,如果你的代码是这样的:

1
2
3
Form1 := TForm1.Create(...)
Form1.ShowModal;
Form1.Free;

您应该以 nil 作为所有者来编写它,并在 try..finally 块中保护它:

1
2
3
4
5
6
7
8
9
10
11
procedure TForm1.Button1Click(Sender: TObject);
var
  AForm: TForm2;
begin
  AForm := TForm2.Create(nil);
  try
    AForm.ShowModal;
  finally
    AForm.Free;  // You know when it will be free'd, so no owner needed
  end;
end;

另一方面,如果您打算将其放置一段时间,请指定一个可以在以后释放它的所有者:

1
2
3
4
5
6
7
8
9
procedure TForm1.Button1Click(Sender: TObject);
var
  AForm: TForm2;
begin
  AForm := TForm2.Create(Application);
  // Here you don't know when it will be free'd, so let the
  // Application do so
  AForm.Show;
end;

如果按照我在此处演示的方式进行操作,这些技术都不会导致访问冲突。请注意,在这两种情况下,我都没有使用 IDE 生成的 Form2 变量,而是使用了本地变量以避免混淆。那些 IDE 生成的变量是邪恶的(除了必需的 Form1 或代表主窗体的任何名称,它必须由应用程序自动创建和拥有)。除了主窗体的 var 之外,我总是立即删除该自动生成的变量,并且从不自动创建任何东西,除了可能的数据模块(可以在主窗体之前自动创建没有任何问题,因为数据模块不能是主窗体).


组件 Owner 的任务是在您的所有者被销毁时销毁所有拥有的组件。

Application 对象在终止时被销毁,因此如果您依赖它来销毁您的表单,那么直到终止才会发生这种情况。

这里的关键点是分配所有者控制谁销毁拥有的组件,以及何时销毁。

在你的情况下,你有一个模态形式,你想有一个短暂的生命。总是这样写:

1
2
3
4
5
6
Form := TMyModalForm.Create(nil);
try
  Form.ShowModal;
finally
  Form.Free;
end;

既然你明确地销毁了它,给它们一个所有者是没有意义的。并确保 Form 是一个局部变量。

如果您确实通过了所有者,这不会特别受伤。这只是浪费,因为所有者被告知其责任,然后通知它不再负责。

但如果你这样做:

1
2
Form := TMyModalForm.Create(Self);
Form.ShowModal;

然后,每次您显示模态表单时,您都会泄漏一个表单,该表单在拥有的表单被销毁之前不会被销毁。如果您将 Application 设为拥有,则模态表单将被泄露,直到终止。

形式之间的所有权对于例如主要形式和与主要形式一样长寿的无模式亲属来说是合理的。无模式窗体可以归主窗体所有,然后在主窗体销毁时自动销毁。

但是如果主窗体包含对无模式窗体的引用,那么我可能只是让它无主并从主窗体的析构函数中显式销毁。

@dummzeuch 指出如果您将 Position 设置为 poOwnerFormCenter,则框架希望您提供一个表单作为所有者。在我看来,这是一个糟糕的设计,它将生命周期管理与视觉布局混为一谈。但这就是设计,所以你不得不接受它。虽然没有什么可以阻止你明确地销毁一个拥有的组件。你可以这样做:

1
2
3
4
5
6
7
Form := TMyModalForm.Create(Self); // Self is another form
try
  Form.Position := poOwnerFormCenter;
  Form.ShowModal;
finally
  Form.Free;
end;

当您销毁表单时,会通知其所有者,并且已销毁的表单会从所有者的拥有组件列表中删除。

主要形式本身很有趣。它必须由 Application 拥有,因为必须通过调用 Application.CreateForm 创建主窗体。这是您唯一应该调用 Application.CreateForm 的时间。主窗体通常是Application 应该拥有的唯一窗体。特别是如果您采用让其他表单不拥有或由生成它们的表单拥有的策略。

但是如果你让主窗体在终止时被销毁,当Application被销毁时,你可能会被抓到。以这种方式编码时,我在终止时遇到了间歇性运行时错误。我的补救措施是明确销毁主窗体作为主 .dpr 文件主体的最后一幕。即Application.Run返回后销毁主窗体。