8

在我的代码中,我使用了一个在不同位置创建的小型数据存储类。为了避免内存泄漏和简化事情,我想使用引用计数,所以我做了

type TFileInfo = class (TInterfacedObject, IInterface)

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

为什么不收集 TInterfacedObject 的后代垃圾?

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

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

是否有任何其他可能使 Delphi 中的对象使用引用计数?

4

7 回答 7

8

Delphi 中的引用计数仅在您仅通过接口引用您的实例时才有效。一旦你混合了接口引用和类引用,那么你就有麻烦了。

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

  1. Barry Kelly 写了一篇关于智能指针的文章。它使用 Delphi 2009 中的泛型,但我敢肯定,如果您尚未使用 2009,您可以将其硬编码为您正在使用的特定类型的版本(顺便说一句,这是一个很棒的版本)。

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

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

这就是关于德尔福的事情,有一种自由的方式来完成事情。选项 #1 在我看来是最好的选择 - 获取 Delphi 2009 并尽可能使用该方法。

祝你好运!

于 2009-04-23T17:03:51.013 回答
6

不幸的是,只有在您使用接口(在您的情况下为自定义接口 IFileInfo)时,Delphi 编译器才会生成必要的代码来增加/减少引用计数。此外,如果接口被强制转换为指针(或 TObject),那么引用计数也是不可能的。例如,假设全局变量列表:TList:

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

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

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

于 2009-04-23T10:42:10.977 回答
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;
于 2009-04-23T14:37:16.277 回答
3

如果您想消除对 TObject 实例的 free 调用,那么您可能需要查看本机 Delphi 的垃圾收集器。我知道 2 种不同的垃圾收集器和一种垃圾收集技术,每种都有优缺点。

其中之一可能对你有用。

于 2009-04-23T18:38:34.610 回答
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
于 2009-04-23T10:36:13.907 回答
1

为了补充已经说过的内容,如果要存储对接口的引用,而不是使用 TList,请使用TInterfaceList。参考计数将始终如一地工作。

于 2009-04-23T17:14:17.010 回答
0

对此有很长的解释,但简而言之:从 TInterfacedObject 继承(而不是自己调用 Free)是不够的,您需要使用 object-factory-dynamic 为您创建对象,并使用接口指针指向无处不在的对象,而不仅仅是对象引用变量。(是的,这意味着您不能不查看就切换“旧代码”)

于 2009-04-23T11:04:36.413 回答