对象的引用计数

8

在我的代码中,我使用了一个小型的数据存储类,在不同的位置创建。为了避免内存泄漏并简化操作,我想使用引用计数,因此我进行了以下操作:

type TFileInfo = class (TInterfacedObject, IInterface)

我曾经移除了所有手动调用TFileInfo.Free的代码,但不幸的是Delphi报告了很多内存泄漏。在Stack Overflow上搜索后,我发现以下问题解释了为什么这样做行不通:

为什么TInterfacedObject的派生类不能进行垃圾回收?

那里提供了一个解决方法,但它需要我(至少我理解是这样)编写一个自定义接口IFileInfo,并为其提供许多getter和setter,而我想避免这样做。

编辑 我应该补充说明一下,我将创建的FileInfo对象插入到两种不同类型的哈希表中:一种从TBucketList继承,另一种是Codegear论坛的哈希映射实现。在内部,它们都使用指针,因此情况就像其他问题一样。

还有其他可能使Delphi中的对象使用引用计数的方法吗?

7个回答

8
在Delphi中,引用计数仅在您仅通过接口引用实例时才起作用。一旦混合使用接口引用和类引用,则会出现问题。
基本上,您希望进行引用计数,而无需创建一个包含所有方法和属性定义的接口。有三种方法可以做到这一点,大致按我推荐的顺序排列。
  1. Barry Kelly写了一篇关于智能指针的文章。它使用Delphi 2009中的泛型,但我相信如果你还没有使用2009,你可以硬编码到你正在使用的特定版本的类型(顺便说一句,这是一个很棒的版本)。

  2. 另一种适用于更多版本的Delphi且修改较少的方法是Janez Atmapuri Makovsek的值类型包装器。这是一个以TStringList为例实现的示例,但你可以为任何类型进行调整。

  3. 第三种方法是创建一个接口指针(类似于Barry的智能指针,但不那么智能)。我相信JCL中有一个,但我不确定详细信息。基本上,这是一个在构造时接受TObject引用的接口。然后,当它的引用计数达到零时,它会在你传递给它的对象上调用free。这种方法只适用于短暂存在的实例,你不能将其作为参数传递,因为你将引用计数引用与实际使用的引用分开了。我建议使用其他两种方法之一,但如果你喜欢这种方法并想要更多信息,就让我知道。

这就是 Delphi 的好处,有多种免费的方法可以实现同样的事情。在我看来,选项 #1 是最好的 - 如果可以的话,获取 Delphi 2009 并使用该方法。

祝你好运!


我再次阅读了您的问题,我不确定这些答案是否有效,因为您的列表接受对象,因为它们使用指针。这些方法要么使用接口,要么使用记录。同样,升级到Delphi 2009提供了可以与记录、接口、对象或本地类型一起使用的通用容器。 - Jim McKeeth
虽然我认为你的回答有一些好的观点,但是这三种方法都不能解决OP所遇到的问题 - 它们都不能代替TObject实例使用,以消除调用Free()的需要。 - mghie
@JimMcKeeth:我正在使用Delphi 2009,但它仍然缺乏一种通用且合理高效的哈希映射实现,对吧? - jpfollenius
Delphi 2009有一个TDictionary,但是我不知道它相对于其他的数据结构如何高效。由于它是泛型的,因此可以通过接口引用而不是对象引用将东西放入其中。 - Jim McKeeth
虽然在 Delphi 2009 中,你可以使用 Barry Kelly 的智能指针并进行很少的修改,这可能也可以解决你的问题。 - Jim McKeeth

6

不幸的是,Delphi编译器只有在使用接口(在您的情况下为自定义接口IFileInfo)时才生成必要的增加/减少引用计数的代码。此外,如果将接口转换为指针(或TObject等),则无法进行引用计数。例如,假设全局变量list:TList:

var ifi : IFileInfo;
begin
  ifi := TFileInfo.Create;
  list.Add(TFileInfo(ifi));
end;

方法返回后,list[list.Count - 1]会包含悬空指针。

因此,在将接口强制转换为指针的哈希映射中不能使用接口,哈希映射实现必须将它们保留为 IInterface。


如果我将所有对象插入哈希映射表中,编译器是否会正确生成inc/dec操作? - jpfollenius
这段代码无法编译。将接口强制转换为对象是不可能的。 - dummzeuch
它确实编译通过了,但是它并不起作用,这正是我试图证明的重点。我已经再次编辑了答案,希望现在更清晰了。 - Kcats
很难选择接受的答案,但我发现你的回答是对实际问题(基于指针的数据结构)最清晰的描述。 - jpfollenius

4
不要混淆对象引用和接口引用。
var
  Intf: IInterface;
  Obj: TFileInfo;

begin
  // Interface Reference
  Intf := TFileInfo.Create; // Intf is freed by reference counting, 
                            // because it's an interface reference
  // Object Reference
  Obj := TFileInfo.Create;
  Obj.Free; // Free is necessary

  // Dangerous: Mixing
  Obj := TFileInfo.Create;
  Intf := Obj; // Intf takes over ownership and destroys Obj when nil!
  Intf := nil; // reference is destroyed here, and Obj now points to garbage
  Obj.Free; // this will crash (AV) as Obj is not nil, but the underlying object
            // is already destroyed
end;

3

这个功能适用于接口,但不适用于对象。

你可以创建类似的功能,但需要重写 TObject 的一些结构:

TRefCountObject = class (TObject)
private
  FRefCount : Integer;
public
  constructor Create;

  procedure Free; reintroduce;

  function RefCountedCopy: TRefCountObject;
end;


constructor TRefCountObject.Create;
begin
  inherited;
  FRefCount := 1;
end;

procedure TRefCountObject.Free;
begin
  if self=nil then Exit;
  Dec(FRefCount);
  if FRefCount<=0 then
    Destroy;
end;

function TRefCountObject.RefCountedCopy: TRefCountObject;
begin
  Inc(FRefCount);
  Result := self;
end;

您需要使用RefCountedCopy将对象分配给另一个变量。但是,这样您会得到一个引用计数的对象。

如何使用:

var1 := TRefCountObject.Create;   // rc = 1
var2 := var1.RefCountedCopy;      // rc = 2
var3 := var1.RefCountedCopy;      // rc = 3
var2.Free;                        // rc = 2
var1.Free;                        // rc = 1
var4 := var3.RefCountedCopy;      // rc = 2
var3.Free;                        // rc = 1
var4.Free;                        // rc = 0

谢谢您详细的回答!不过我还是没完全理解。我还需要调用TRefCountObject.Free吗?或者我该如何使用它? - jpfollenius
那么我仍然必须确保为每个对象至少调用一次Free,对吗?没有办法避免这种情况吗? - jpfollenius
1
这个问题没有万能的解决方案。 - Lasse V. Karlsen
据我理解,您的问题是您有一个对象被两个列表引用。通常情况下,您将其中一个声明为“所有者”,负责销毁和删除其他链接。但即使您只有一个列表,您也需要释放每个对象。或者使用具有垃圾回收功能的语言。 - Toon Krijthe
好的,那我会回到手动释放对象内存的版本。我只是好奇是否有一种方法可以通过接口“模拟”普通对象的垃圾收集。正如你和其他帖子中指出的那样,这是不可能的。感谢提供宝贵信息! - jpfollenius

3

1
补充一下之前的讨论,如果你想要存储接口的引用,而不是使用 TList,请使用 TInterfaceList。这样引用计数会保持一致。

问题是:我需要一个高效的哈希映射实现。我正在使用的那个(Barry Kelly在codegear论坛上发布过,如果我没记错的话)使用指针,所以我无法使用接口,除非我有一个存储接口的哈希映射实现。 - jpfollenius

0

对此有一个长的解释,但简而言之:从 TInterfacedObject 继承(并不自己调用 Free)是不够的,您需要使用对象工厂动态为您创建对象,并且在任何地方都使用接口指针而不仅仅是对象引用变量。(是的,这意味着您不能只是切换“旧代码”而不检查它)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接