我最近阅读了很多关于此的内容,但从未找到最终答案。
所以,例如,如果我写:
Form1 := TForm1.Create(Application);
应用程序应该负责从内存中释放表单,对吗?
为什么人们通常会这样做:
1 2 3
| Form1 := TForm1.Create(Application);
Form1.ShowModal;
Form1.Free; |
??
在某些地方看到,如果您尝试"释放"一个已经被释放的对象,您会收到一个 EAccessviolation 消息,但正如我测试的那样,它并不总是正确的。
所以请,这实际上是如何工作的?
这个 EAccessviolation 事情快把我逼疯了,我怎么能完全理解这个事情??我在哪里可以找到这些宝贵的信息!??
- 该代码中没有双重免费。当表单被释放时,它会调用它的所有者(应用程序),以便应用程序可以将它从要销毁的组件列表中删除。
-
等待不是 Free 方法的目的是检查对象是否仍然存在,然后才调用对象的默认析构函数?我知道如果对象不再存在或尚未创建,直接调用对象析构函数将始终导致 EAccesViolation。
-
@Silver 在 Q Free 和 Destroy 的代码中是可以互换的,因为在 Form1.Free 中,Form1 不能为零。假设没有其他方在使用该 var。如果您遵循 Delphi IDE 的 VB 兼容模式并使 Form1 成为全局变量,则很难做出假设。
-
@DavidHeffernan我知道在有问题的代码中Form1当时不能为零。但我问一般是因为我总是为我创建的每个表单分配一个所有者,即使你计划在以后自己销毁该表单之前销毁它的所有者。到目前为止,按照这种模式,当所有者试图破坏已经不存在的形式时,我从未得到过 EAccesViolation。所以我只想知道这是否是可接受的方法。
-
@silver 我的建议是永远不要分配所有者
-
@DavidHeffernan 介意告诉我为什么?
-
@Silver 对于一种形式,所有者通常是毫无意义的。显式销毁允许您控制它何时发生。例如,一个模态表单可能会显示很多次。每次都有一个新实例,并且由主窗体拥有,你就有了泄漏。
-
@DavidHeffernan 我不同意表单所有者毫无意义的事实。例如,在下一个场景中,它可能非常有用。想象一下,您有第二个表单,例如允许您从列表中选择一些产品。然后,您可能希望在第三种表格中按需提供有关此产品的一些附加信息。因此,如果稍后您决定关闭 seconf 表单,您可能还想关闭/销毁第三种表单,因为单独显示它只会造成混淆,因为您不知道附加数据属于哪个产品。
-
@DavidHeffernan 是的,您可以修改第二个表单 OnClose 事件以调用第三个表单的 Close 操作,但是您可以通过在创建第三个表单时将第二个表单指定为第三个表单的所有者来实现相同的目的。
-
@Silver 在参数中传递该信息。不要将其与组件所有权混为一谈,这是一个不相关的事情。
-
如果您需要拥有的组件与拥有的组件一样长的寿命,@silver Owner 很好。非常适合表单上的大多数组件。但是对于表单本身,这通常不是您想要的。
-
@DavidHeffernan 将该信息作为参数传递?你有什么信息,从哪里到哪里。
-
@Silver 好的,我误解了。当然,如果您不想保留对另一个表单的引用,并且希望在第一个表单被销毁时将其销毁,那么所有者就可以了。
-
@SilverWarior在我的回答中,表单之间的所有权涵盖了对于主表单和与主表单一样长的无模式亲属来说是合理的。无模式窗体可以归主窗体所有,然后在主窗体销毁时自动销毁。
一般规则是:
-
如果您要自己释放它,请使用 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 之外,我总是立即删除该自动生成的变量,并且从不自动创建任何东西,除了可能的数据模块(可以在主窗体之前自动创建没有任何问题,因为数据模块不能是主窗体).
- 肯你能扩展你的答案吗?我想知道您为什么不喜欢自动生成/IDE 生成的变量?
-
在内存管理的上下文中,使用 Application 作为所有者是没有意义的。所有者在销毁时释放拥有的对象,并且由于 Application 对象将在程序退出时被销毁,因此您仍然存在内存泄漏。当程序退出时,无论如何都会释放所有内存,因此使用 Application 作为所有者并没有什么区别。
-
"指定一个负责释放它的所有者"已经说过一切。
-
正如@ain 所说,保留应用程序拥有的表单可能会导致泄漏。
-
@RawN:IDE 生成的变量名是全局的,这总是要避免的。全局变量会导致各种各样的问题,比如引导你访问你不想要的东西,释放你不想释放的东西,或者认为你已经释放了你不想??释放的东西。变量范围应该尽可能的有限,而全局变量几乎总是一个可怕的想法。由于 Delphi 应用程序的工作方式,要求主窗体是自动创建的并且是全局的。由于上述原因,我删除了其他人。
-
@KenWhite:我同意。一旦 IDE 创建它们,我就删除了爆炸的表单变量。否则,我会发现我的一些开发人员使用它们从其他表单中访问控件!此外,如果您允许表单的多个副本,那么一个变量将无济于事。
组件 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返回后销毁主窗体。
-
还有一个没有人提到的问题:TForm.Position 允许您指定打开表单时表单的位置。它有一个可能的值 poOwnerFormCenter ,它应该将表单集中在所有者表单上。如果你通过 NIL,它就不能。但是如果你通过了Application,它会做什么呢?
-
如果 Owner is TCustomForm 则使用 TCustomForm(Owner),否则使用 Application.MainForm。
-
@DavidHeffernan 在我正在使用的项目中,我也将 Application.CreateForm 用于数据模块。看到您的帖子后,我尝试根据您的示例对其进行修改,结果出现了新的 Eaccessviolation 异常。任何猜测为什么?
-
好的,数据模块超出了我的专业领域。我从不使用它们,不是我的风格。但是,我相信您应该能够在不使用 CreateForm 的情况下创建数据模块。但是,通常您需要一个数据模块,因为您将其全局公开给所有人和杂项。因此,如果您对 dm 使用局部变量而不是全局变量,您可能会遇到问题。无论如何,如果没有看到更多,我有点难以从这里说出来。
-
@DavidHeffernan好吧,先生,现在我很好奇..我知道完整的解释会很广泛,但简而言之,那你用什么?最近在这个 Eaccessviolation 问题上遇到了很多麻烦,以至于在我的新项目中,我想尽可能少地保持全局变量,这与 dm 完全相反......
-
访问违规的原因很多。也许它不是由你认为的原因造成的?我个人不使用数据模块,因为我认为它们是一种相当糟糕的设计形式。鼓励大量耦合并使其难以分层。
-
数据模块是全局 IDE 创建的变量和自动创建表单的另一个例外,特别是当需要从主表单访问数据模块时。在这种情况下,保留数据模块的全局变量,并使用 Project->Options->Forms 将数据模块移动到自动创建列表中的主表单上方。数据模块不能是主要形式,所以先创建它们是安全的。