【问题标题】:Delphi - Why is TObject.InitInstance public?Delphi - 为什么 TObject.InitInstance 是公开的?
【发布时间】:2014-03-20 17:25:38
【问题描述】:

我对 Delphi 有点陌生,这个问题只是我好奇。 (我也只是偶然尝试使用它,结果发现我不应该这样做。)

如果您查看 TObject.InitInstance 的文档,它会告诉您不要使用它,除非您覆盖 NewInstance。该方法也是公开的。如果用户永远不应该调用它,为什么不让它受到保护呢?

【问题讨论】:

  • 可能是历史意外。很可能只有原作者才能告诉你确切的答案。
  • 我可能天真地认为可能有实际原因。
  • 肯定是有原因的。只是知道答案的极少数人不太可能来这里。
  • TObject 的所有内容都是公开的。保护 InitInstance 是不公平的。
  • @Sertac 有人选择公开所有内容。

标签: delphi


【解决方案1】:

自从我在 1992 年中期左右开始整个德尔福的事情时就在身边,这个问题可能有几个答案。如果您查看 Delphi 1 中 TObject 的原始声明,TObject 上没有任何受保护/私有成员。这是因为在 Delphi 开发的早期并且与向语言中引入异常相一致,异常是从与其他对象不同的堆中分配的。这就是 NewInstance/InitInstance/CleanupInstance/FreeInstance 函数的起源。在您的类类型上覆盖这些函数,您可以从字面上控制分配对象的位置

近年来,我使用此功能创建了一个对象实例的缓存,这些实例实际上是“回收”的。通过拦截 NewInstance 和 FreeInstance,我创建了一个系统,其中实例在取消分配时不会返回到堆中,而是放置在无锁/低锁链表上。这使得分配/释放特定类型的实例更快,并消除了内存管理器的大量偏移。

通过公开 InitInstance(与之相反的是 CleanupInstance),这将允许从其他实用程序函数调用这些方法。在上面我提到的情况下,可以在现有内存块上调用 InitInstance,而不必仅从 NewInstance 调用。假设 NewInstance 调用管理上述缓存的通用函数。类实例的“范围”丢失了,因此调用 InitInstance 的唯一方法是公开的。

这些天,我们可能会发布执行我上面描述的代码......现在它是内部“研究”项目的一部分。

哦,顺便说一句,也有点历史教训...在 Delphi 1 版本之前,如何分配/释放异常实例的设计返回到使用与所有其他对象相同的堆。由于整体集体失误,假设我们需要分配所有异常对象实例以“保护”内存不足的情况。我们推断,如果我们尝试引发异常,因为内存管理器“内存不足”,我们将如何分配异常实例!?我们已经知道那时没有记忆!所以我们决定对所有异常都需要一个单独的堆......直到 Chuck Jazdzewski 或 Anders Heijlsberg(我忘记具体是哪一个)找到一个简单、相当聪明的解决方案......只需 预先分配 em> 启动时内存不足异常!我们仍然需要控制是否应该真正释放异常(异常实例在处理后会自动释放),因此整个 NewInstance/FreeInstance 机制仍然存在。

【讨论】:

  • 很高兴听到认识的人的来信。我曾经偶然发现这个预先分配的内存不足异常,我认为它确实很聪明。 :)
  • 希望有一天你能回来,让德尔福再次伟大。
【解决方案2】:

永远不要说永远。在 VCL 中,太多的东西是私有的,而不是虚拟的,所以我有点喜欢这些东西是公共的这一事实。

正常使用不是必须的,但在特定情况下,您可能会使用它来批量分配对象。 NewInstance 为对象保留一点内存,然后调用 InitInstance 对其进行初始化。您可以编写一段代码,一次性为大量对象分配内存,然后为该大块的不同部分调用 InitInstance 以初始化其中的不同块。这样的实现可以作为flyweight pattern 实现的基础。

通常你根本不需要这样的东西,但如果你真的想要/需要的话,你可以这样做很好。

它是如何工作的?

有趣的是:Delphi 中的构造函数只是一些方法。 Create 方法本身并没有做任何特别的事情。如果你看它,它和其他任何方法一样只是一种方法。在 TObject 中它甚至是空的!

您甚至可以在实例上调用它(调用 MyObject.Create 而不是 TMyObject.Create),它根本不会返回新对象。关键在于constructor 关键字。这告诉编译器,在执行 TAnyClass.Create 方法之前,它还应该构造一个实际的对象实例。

这种构造基本上意味着调用NewInstanceNewInstance 为对象的数据分配一块内存。之后,它调用InitInstance 对该内存进行一些特殊的初始化,首先是清除它(用零填充)。

分配内存是一项相对昂贵的任务。内存管理器(编译到您的应用程序中)需要找到一块空闲内存并将其分配给您的对象。如果它没有足够的可用内存,它需要向 Windows 发出请求以提供更多内存。如果您要创建数千甚至数百万个对象,那么这可能是低效的。

在极少数情况下,您可以决定一次性为所有这些对象分配内存。在这种情况下,您根本不会调用构造函数,因为您不想调用 NewInstance(因为它会分配额外的内存)。相反,您可以自己调用 InitInstance 来初始化您的大块内存。

无论如何,这只是一个假设的原因。也许根本就没有理由。我在 VCL 中看到了很多不合理应用的可见性级别。也许他们根本就没有考虑过。 ;)

【讨论】:

  • 那么一些罕见的构造案例如何证明可以公开一个您可以保护的方法,这意味着它仍然可以访问?
  • 如果问题是如何最好地实现对象池,那将是合适的,但这个问题是对 InitInstance 为什么会公开的可能解释,如果你(只是)覆盖 NewInstance 和 FreeInstance 来做一些智能内存管理。另外,如果你愿意,你仍然可以调用构造函数,也就是实例的Create方法。
  • 您知道访问受保护成员的简单技巧吗?如果您足够聪明,可以围绕内存管理器实现自己的内存管理,那么您也可以使用它。这几乎不是这种方法公开的原因
  • @StefanGlienke 我知道这个技巧,但对我来说这感觉像是一个 hack,它之所以有效,是因为 Delphi 中的 protected 不是 strict protected,并且允许整个单元中的任何代码访问.这很好,如果你需要的话,但是如果我可以调用一个方法而不必求助于这样的黑客,我是一个快乐的露营者。使用您的论点,我们可以完全跳过public,让人们破解(或“欺骗”)他们想要调用的每个方法。
  • 我知道一家公司正是这样做的。他们使用了如此多的内存,以至于他们希望最大限度地减少内存管理器的开销,并以这种方式分配和初始化大块对象。他们还进行其他繁重的工作,例如动态构建和修补 VMT。不是您的平均代码(:
【解决方案3】:

它为开发人员提供了一种不使用 NewInstance(来自堆栈/内存池的内存)创建对象的方法

【讨论】:

  • 呃,那怎么办?执行构造函数肯定会导致调用NewInstance。它覆盖了NewInstance,允许您从对象池中分配。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-17
  • 1970-01-01
  • 1970-01-01
  • 2019-09-13
  • 2018-12-23
  • 2011-01-04
  • 1970-01-01
相关资源
最近更新 更多