对象的引用计数

时间:2009-04-23 10:10:10

标签: delphi interface reference-counting tinterfacedobject

在我的代码中,我使用了一个小型数据存储类,它在不同的地方创建。为了避免内存泄漏并简化操作,我想使用引用计数,所以我做了

type TFileInfo = class (TInterfacedObject, IInterface)

并删除了我对TFileInfo.Free的所有手动调用。不幸的是Delphi报告了很多内存泄漏。搜索SO我发现以下问题解释了为什么这不起作用:

Why aren't descendants of TInterfacedObject garbage collected?

有一个解决方法,但它需要我(至少如果我做对了)编写一个自定义接口IFileInfo并为它提供了许多getter和setter,我想避免。

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

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

7 个答案:

答案 0 :(得分:8)

Delphi中的引用计数仅在您通过接口仅引用实例时才有效。只要混合接口引用和类引用,就会遇到麻烦。

基本上,您需要引用计数,而无需使用其中定义的所有方法和属性创建接口。有三种方法可以做到这一点,这些方法大致按我推荐的顺序排列。

  1. Barry Kelly写了一篇关于Smart Pointers的帖子。它使用Delphi 2009中的Generics,但我很确定你可以将它硬编码到你正在使用的特定版本的类型,如果你还没有使用2009(它是一个很棒的版本BTW)。

  2. 使用更多版本的Delphi和更少修改的另一种方法是Janez Atmapuri Makovsek的value type wrapper。这是为TStringList实现的示例,但您可以针对任何类型进行调整。

  3. 第三种方法是创建一个接口指针(类似于Barry的智能指针,但不是那么聪明)。我相信JCL中有一个,但我不记得确切的细节。基本上这是一个在构造时接受TObject引用的接口。然后,当它的引用计数达到零时,它会在您传递给它的对象上调用free。此方法实际上仅适用于未作为参数传递的短期实例,因为您将引用计数引用与实际使用的引用分开。我建议使用其他两种方法中的一种,但如果您更喜欢这种方法并想了解更多信息,请告诉我。

  4. 这就是德尔福的事情,有一种自由的方式来完成事情。在我看来,选项#1是最好的 - 如果可以的话,获取Delphi 2009并使用该方法。

    祝你好运!

答案 1 :(得分:5)

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

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

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

因此,接口不能用于将它们转换为指针的hashmap中,hashmap实现必须将它们保留为IInterface。

答案 2 :(得分: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将对象分配给另一个变量。但是你有一个refcounted对象。

如何使用:

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

答案 3 :(得分:3)

不要混合对象引用和接口引用。

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;

答案 4 :(得分:3)

如果你想要消除对TObject实例的免费调用,那么你可能想要查看本机Delphi的垃圾收集器。我知道2种不同的垃圾收集器和垃圾收集技术,每种都有利弊。

其中一个可能适合你。

答案 5 :(得分:1)

要添加已经说过的内容,如果要存储对Interfaces的引用,而不是使用TList,请使用 TInterfaceList 。引用计数将始终如一。

答案 6 :(得分:0)

对此有一个很长的解释,但简而言之:继承TInterfacedObject(而不是自己调用Free)是不够的,你需要使用object-factory-dynamic为你创建对象,并使用interface-到处都是对象的指针,而不仅仅是对象引用变量。 (是的,这意味着你不能只是在不查看的情况下切换'旧代码')