5

我对 Delphi 相当陌生,并且一直在手动进行所有内存管理,但听说过 Delphi 能够使用接口进行引用计数并以这种方式提供一些内存管理的引用。我想开始,但有几个问题。

  1. 只是一般情况下,我该如何使用它。创建接口和实现它的类。那么每当我需要该对象时,变量实际上是否属于接口类型,但实例化对象并立即执行?不用考虑释放它吗?没有更多的尝试?

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

  3. 哪些常见的陷阱可能会让我感到悲伤?例如:将接口对象转换为其类的对象会破坏我的引用计数吗?还是有任何不明显的方式 Delphi 会创建参考循环?(意思是除了 A 使用 B 使用 C 使用 A)

如果有涵盖任何这些的教程,那就太好了,但我在搜索中没有提出任何内容。谢谢。

4

5 回答 5

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() 调用放在方法的顶部,尽可能靠近它们引用的变量声明,然后我们甚至创建了任何对象。

实现的全部细节,包括源代码和更多示例,都在我的这篇博文

于 2012-08-02T20:53:52.947 回答
8

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

我个人的结论是,您最终会得到很多过于复杂的代码,没有比“我不必担心免费调用”更好的理由

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

1) 您正在使用出于 COM 兼容性目的而存在的副作用。

2)您正在使您的对象占用空间和效率更重。接口是指向指针列表的指针..或类似的东西。

3)就像你说的......你现在必须为了避免自己释放内存的唯一目的而制作成堆的接口......在我看来,这会带来更多的麻烦而不是它的价值。

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

现在回答你的问题。

1)给定 TFoo 和接口 IFoo 你可以有一个类似下面的方法

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

...而且 presto,你不需要 finally 来释放它。

2) 是的,就像我说的,你认为这是个好主意,但它变成了布基斯的巨大痛苦

3)2个问题。

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

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

于 2012-08-02T20:28:05.683 回答
3

接口的内存管理是通过TInterfacedObject实现的_AddRef_Release实现的。

一般来说,使用接口来减少内存管理的麻烦可能是一个好主意,但您需要注意以下事项:

  • 确保实现接口的类派生自TInterfacedObject或滚动您自己的祖先类,该类为_AddRef_Release
  • 使用非此即彼:所以无论是用户界面引用,还是使用对象实例引用,都不要混合使用。在组件中实现接口时可能会出现问题(因为这些接口源自TComponent,而不是TInterfacedObject
  • 不要采用TInterfacedComponent方式,因为它混合了基于Owner的内存管理和_AddRef/_Release基于内存的管理
  • 观看循环接口引用(您可以到处实现此处提到并在此处实现的“弱接口引用” )
  • 您需要维护额外的代码,因为您需要为要公开的类的部分定义接口,并使这两者保持同步(您可以为此使用Model Maker Code Explorer;它允许您提取接口并总体上提升您的开发,因为它以单一动作管理代码的接口/实现部分)
  • 您需要一些额外的管道来创建底层类的实例。您可以为此使用工厂模式

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

于 2012-08-03T06:51:17.150 回答
0

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

学习惯用的 delphi 风格并尝试模仿它,不要争吵。可悲的是,人们认为“针对接口而不是实现的程序”意味着“到处使用 IUnknown”。这不是真的。我建议您不要使用 COM IUnknown 接口,而是使用抽象基类。您唯一不能做的就是在一个类中实现两个抽象基类,这种需求很少见。

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

于 2012-08-06T00:19:05.140 回答
0

仅仅为了避免手动Free而切换到界面是没有意义的。Free/try-finally 行的经济性几乎无法弥补在接口中同时声明 g/setter 和属性的必要性,更不用说保持 intf/class 声明同步的必要性了。由于隐式完成代码和引用计数,接口也会带来性能损失。如果性能不是重点,而您想要实现的只是自动释放,我建议使用一些通用接口包装器,如 Deltics 建议的那种。

于 2015-01-12T08:32:24.187 回答