【问题标题】:Delphi Ownership Confusion德尔福所有权混乱
【发布时间】:2011-10-27 21:15:57
【问题描述】:

我一直认为,销毁可视化控件是所有者负责的,如果我将nil作为所有者传递,我可以手动控制销毁。

考虑以下示例:

TMyForm = class (TForm)
private
  FButton : TButton;
end;

...
FButton := TButton.Create(nil);   // no owner!!
FButton.Parent := Self;

我希望这个按钮会产生内存泄漏,但它不会,实际上调用了TButton 的析构函数。

进一步调查表明,TWinControl 析构函数包含以下 sn-p 代码:

I := ControlCount;
while I <> 0 do
begin
  Instance := Controls[I - 1];
  Remove(Instance);
  Instance.Destroy;
  I := ControlCount;
end;

看起来它正在破坏子组件(Parent 设置为控件本身的组件)。

我没想到父控件会破坏控件。谁能解释为什么会这样?如果我传入所有者,谁在销毁该对象?

【问题讨论】:

  • TComponent.DestroyComponents(从析构函数调用)在您不设置父对象但设置所有者时进行销毁。我从来没有注意到 TWinControl 也会发生破坏,这很高兴知道。
  • 在问题How to detach a panel and show it in a separate window? 中有些类似的混淆。 TControl.Parent Property 的“注释”中有解释,但我觉得解释有点混乱(关于流媒体部分)..

标签: delphi memory-management components delphi-xe ownership


【解决方案1】:

为什么会这样?

这是有道理的,而且是设计使然。当父级被销毁时,您认为孤立的子级控件应该发生什么?他们是否应该突然开始作为顶级窗口浮动?可能不是。是否应该将它们重新设置为另一个控件?哪一个?

who is destroying the object if I pass in an owner?

Parent,如果它首先被分配并被释放。 TWinControl 覆盖TComponent 的析构函数以释放其子控件first(继承的析构函数仅在以后调用)。孩子控制notify 他们的Owner 关于被销毁,这会将它们从其拥有的组件列表中删除。这就是为什么所有者稍后不会尝试在其析构函数中再次释放您的对象。

如果ParentOwner 是同一个对象,则上述内容也适用。

如果ParentOwner 是两个不同的对象,并且您先释放所有者,那么所有者组件将释放其所有拥有的组件(请参阅TComponent's destructor)。您的对象是TControl 的后代,TControl 覆盖析构函数以调用SetParent(nil);,这会从父控件的子控件列表中删除该实例。这就是为什么父级稍后不会尝试在其析构函数中再次释放您的对象。

【讨论】:

  • +1 谢谢!很好的解释,即使我会发现如果只为子控件执行SetParent(nil) 会更有说服力。我的意思是,整个组件所有权原则是为对象销毁而构建的,并且由于TWinControl 继承自TComponent,我不明白为什么第二种机制是必要的。只是使销毁过程更加复杂。或者您是否知道除了组件所有权之外真的需要这样做的示例?
  • 哦,我以为我在第一部分已经解释过了。 “孤儿控件应该怎么办?”因为父母承担清理孩子的责任,就像所有权承担清理拥有组件的责任一样。请记住,Parent 和 Owner 可能是两个不同的对象。
  • 我可能会监督一些事情,但我不明白这一点。每个具有parent 属性的对象也有一个owner 属性。那么parent 为什么要处理对象破坏呢?为什么不使用现有的所有权机制进行销毁? “孤儿控件应该发生什么”:为什么不将Parent 设置为nil
  • TOndrej:我不是说每个分配了父级的对象也分配了一个所有者!我只是说每个这样的对象都有可能来分配一个所有者,所以我认为不需要额外的销毁机制。
  • 清理的两个职责 -> 两种销毁机制。对我来说,需求很明确。
【解决方案2】:

我现在可以访问的最早版本是 Delphi 5,TWinControl 析构函数也有你发布的代码,所以这种行为已经存在很长时间了。而且当您考虑它时,这是有道理的 - Controlsvisual 组件,当您销毁它们的容器(父级)时,销毁子级也是有意义的。 TWinComponent 的析构函数无法为您决定如何处理它们(隐藏它们?将它们重新设置为 Parent.Parent?但是如果当前 Parent 是顶级窗口,即它没有 Parent?等等)。因此,VCL 的设计者决定这是最安全的选择,避免内存/句柄泄漏(尤其是早期的 win 句柄,因此避免泄漏它们可能是重中之重)。所以如果你想让孩子留下来,你应该在销毁容器之前重新养育他们。

顺便说一句。如果你传递了一个 Owner,那么 TComponent.DestroyComponents;(由 TComponent.Destroy 调用)会破坏该组件。

【讨论】:

  • 如果我传入所有者,是什么阻止了Destroy 被调用两次?在这种情况下,应该执行两个析构函数...
  • 对子项的引用从内部列表中删除,因此第二个析构函数不会“看到”(已经销毁的)子项。
  • 但是: TComponent 没有使用Controls 数组(包含子对象),而是使用了Components 列表,控件不应该从中删除.
  • VCL 中有通知系统,它必须处理所有不同的场景,以便及时更新内部所有权列表。即TComponent的析构函数调用if FOwner &lt;&gt; nil then FOwner.RemoveComponent(Self);
  • ain:你能解释得更详细一点吗(也许在编辑你的答案时)?我看不到TWinControl 的析构函数确实从它的所有者的组件列表中删除了控件...
猜你喜欢
  • 2014-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-02
  • 2011-10-04
  • 2012-12-13
相关资源
最近更新 更多