如何在Delphi中有效地使用接口进行内存管理

时间:2012-08-02 19:57:31

标签: delphi delphi-xe

我是Delphi的新手并且已经手动完成了所有的内存管理,但是听说Delphi能够使用接口来进行引用计数并以这种方式提供一些内存管理。我想开始这个,但有一些问题。

  1. 通常,我该如何使用它。创建接口和实现它的类。然后,只要我需要该对象,变量实际上是接口类型,但实例化对象和presto?没有必要考虑解放它吗?没有更多的尝试 - 终极?

  2. 为真正不需要它们的类创建一堆接口似乎非常麻烦。有关自动生成这些的提示吗?我该如何最好地组织它?接口和类在同一个文件中?

  3. 可能导致我悲伤的常见陷阱是什么?例如:将接口对象转换为其类的对象是否会破坏我的引用计数?或者Delphi是否有任何非显而易见的方法可以创建参考循环? (除了A使用B使用C使用A)

  4. 如果有教程可以涵盖其中任何一项,那就太棒了,但我在搜索中没有提出任何建议。感谢。

5 个答案:

答案 0 :(得分:8)

导致Delphi中“自动垃圾收集”的最常见抱怨是,即使是短暂的临时对象也必须手动处理,你必须写出相当数量的“锅炉板”代码,以确保在发生异常时发生这种情况。

例如,为过程中的某些临时排序或其他算法目的创建 TStringList

procedure SomeStringsOperation(const aStrings: TStrings);
var
  list: TStringList;
begin
  list := TStringList.Create;
  try
      :
     // do some work with "list"
      :
  finally
    list.Free;
  end;
end;

正如您所提到的,实现COM引用协议的对象计算了生命周期管理,通过在释放所有对它们的引用后自行清理来避免这种情况。

但由于 TStringList 不是COM对象,因此无法享受此优惠。

幸运的是,有一种方法可以使用COM引用计数来处理这些事情,而无需创建您希望使用的类的所有新的COM版本。您甚至不需要切换到完全基于COM的模型。

我创建了一个非常简单的实用程序类,允许我在轻量级COM容器中“包装”任何对象,专门用于实现自动清理的目的。使用此技术,您可以将以上示例替换为:

procedure SomeStringsOperation(const aStrings: TStrings);
var
  list: TStringList;
begin
  AutoFree(@list);

  list := TStringList.Create;

    :
  // do some work with "list"
    :
end;

AutoFree()函数调用会在编译器为该过程生成的退出代码中创建一个“匿名”接口对象,该对象是 Release()。这个autofree对象被传递一个指向引用你想要被释放的对象的变量的指针。除此之外,这允许我们使用 AutoFree()函数作为伪“声明”,将any和所有AutoFree()调用放在方法的顶部,尽可能接近变量在我们创建任何对象之前,它们引用的声明。

有关实施的详细信息,包括源代码和更多示例,请访问我的博客this post

答案 1 :(得分:7)

我目前正在处理一个非常大的项目,该项目利用接口引用计数的“副作用”来进行内存管理。

我个人的结论是,你最终得到了许多过于复杂的代码而没有更好的理由,“我不必担心自由呼叫”

出于一些非常基本的原因,我强烈建议不要采取这一行动:

1)您正在使用为了兼容COM而存在的副作用。

2)你正在使你的物体足迹和效率更重。接口是指针列表的指针..或者沿着那些行的东西。

3)就像你说的那样......你现在必须制作成堆的界面,其唯一目的就是避免自己释放记忆......这会导致比我认为的更多麻烦。

4)在对象被引用之前,当对象被释放时,最常见的错误将是调试的巨大痛苦。我们在自己的引用中有特殊代码,可以在软件出门之前尝试测试这个问题。

现在回答您的问题。

1)给定TFoo和接口IFoo,您可以使用类似以下的方法

function GetFoo: IFoo;
begin
  Result := (TFoo.Create as IFoo);
end;

...而且,你不需要最终释放它。

2)是的就像我说的那样,你认为这是一个好主意,但它会变成bupkis的巨大痛苦

3)2个问题。

A)你有Object1.Interface2和Object2.Interface1 ......由于循环引用,这些对象永远不会被释放

B)在释放所有引用之前释放对象,我无法强调这些错误是多么难以追踪......

答案 2 :(得分:3)

接口的内存管理是通过由_AddRef实现的_ReleaseTInterfacedObject的实现来完成的。

一般来说,使用界面来减少内存管理是个不错的主意,但是你需要注意这些事情:

  • 确保实现接口的类派生自TInterfacedObject或推送您自己的祖先类,为_AddRef_Release提供良好的实现
  • 使用/或:因此,无论是用户界面引用还是使用对象实例引用,都不要混用它们。在组件中实现接口时可能会出现问题(因为这些接口来自TComponent,而不是TInterfacedObject
  • 不要采用TInterfacedComponent混合基于Owner的内存管理和_AddRef / _Release内存管理
  • 观看循环界面参考(您可以绕过实施“{3}}提及的”弱界面参考“并实施here
  • 您需要维护额外的代码,因为您需要为要公开的类的部分定义接口,并使这两个部分保持同步(您可以here为此;它允许您提取接口和通常会促进您的开发,因为它在单一操作中管理代码的接口/实现部分)
  • 您需要一些额外的管道来创建底层类的实例。你可以Model Maker Code Explorer

这并不总是有效,但确实回答了一些基本问题。

答案 3 :(得分:0)

最短的答案:默认的delphi内存模型是所有者释放他们拥有的对象。所有其他参考文献都是弱引用,必须在所有者之前放手。很少会“共享”一个生命周期短于应用程序整个生命周期的对象。很少进行引用计数,当它完成时,它只由专家完成,否则会增加比它解决的更多错误和崩溃。

学习惯用的德尔福风格并尝试模仿它,不要挣扎。遗憾的是,人们认为“针对接口而不是实现的程序”意味着“随处使用IUnknown”。这不是真的。我建议您不要使用COM IUnknown接口,而是使用抽象基类。你不能做的唯一事情是在一个类中实现两个抽象基类,并且对此的需求很少。

更新:我最近发现使用COM接口(基于IUnknown)帮助我从我的UI类中分离出我的模型和控制器实现是有帮助的。所以我发现使用基于IUnknown的接口很有用。但是,没有很多文档和现有技术可以帮助您做出努力。我希望看到一个“食谱”风格的食谱,为人们提供所有这些,所以他们可以工作,没有通常的问题,结合界面和非界面的生命周期管理,以及你习惯的所有麻烦这种额外的复杂性。

答案 4 :(得分:0)

仅为了避免手动免费而切换到界面是没有意义的。 Free / try-finally行中的经济性很小,很难弥补在界面中声明g / setter和属性的必要性,而没有提到保持intf / class声明同步的必要性。由于隐式的最终化代码和引用计数,接口也会带来性能损失。如果性能不是主要的,并且你想要实现的只是自动生成,我建议使用一些像Deltics建议的那样的通用接口包装器。