我是Delphi的新手并且已经手动完成了所有的内存管理,但是听说Delphi能够使用接口来进行引用计数并以这种方式提供一些内存管理。我想开始这个,但有一些问题。
通常,我该如何使用它。创建接口和实现它的类。然后,只要我需要该对象,变量实际上是接口类型,但实例化对象和presto?没有必要考虑解放它吗?没有更多的尝试 - 终极?
为真正不需要它们的类创建一堆接口似乎非常麻烦。有关自动生成这些的提示吗?我该如何最好地组织它?接口和类在同一个文件中?
可能导致我悲伤的常见陷阱是什么?例如:将接口对象转换为其类的对象是否会破坏我的引用计数?或者Delphi是否有任何非显而易见的方法可以创建参考循环? (除了A使用B使用C使用A)
如果有教程可以涵盖其中任何一项,那就太棒了,但我在搜索中没有提出任何建议。感谢。
答案 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实现的_Release和TInterfacedObject的实现来完成的。
一般来说,使用界面来减少内存管理是个不错的主意,但是你需要注意这些事情:
TInterfacedObject
或推送您自己的祖先类,为_AddRef
和_Release
提供良好的实现TComponent
,而不是TInterfacedObject
)_AddRef
/ _Release
内存管理这并不总是有效,但确实回答了一些基本问题。
答案 3 :(得分:0)
最短的答案:默认的delphi内存模型是所有者释放他们拥有的对象。所有其他参考文献都是弱引用,必须在所有者之前放手。很少会“共享”一个生命周期短于应用程序整个生命周期的对象。很少进行引用计数,当它完成时,它只由专家完成,否则会增加比它解决的更多错误和崩溃。
学习惯用的德尔福风格并尝试模仿它,不要挣扎。遗憾的是,人们认为“针对接口而不是实现的程序”意味着“随处使用IUnknown”。这不是真的。我建议您不要使用COM IUnknown接口,而是使用抽象基类。你不能做的唯一事情是在一个类中实现两个抽象基类,并且对此的需求很少。
更新:我最近发现使用COM接口(基于IUnknown)帮助我从我的UI类中分离出我的模型和控制器实现是有帮助的。所以我发现使用基于IUnknown的接口很有用。但是,没有很多文档和现有技术可以帮助您做出努力。我希望看到一个“食谱”风格的食谱,为人们提供所有这些,所以他们可以工作,没有通常的问题,结合界面和非界面的生命周期管理,以及你习惯的所有麻烦这种额外的复杂性。
答案 4 :(得分:0)
仅为了避免手动免费而切换到界面是没有意义的。 Free / try-finally行中的经济性很小,很难弥补在界面中声明g / setter和属性的必要性,而没有提到保持intf / class声明同步的必要性。由于隐式的最终化代码和引用计数,接口也会带来性能损失。如果性能不是主要的,并且你想要实现的只是自动生成,我建议使用一些像Deltics建议的那样的通用接口包装器。