2009-04-23 86 views
7

在我的代码中,我使用了一个小数据存储类,它在不同的地方创建。为了避免内存泄漏和简化工作,我想使用引用计数,所以我确实使用了引用计数,所以我确实删除了我所有的手动调用TFileInfo.Free的参数。不幸的是Delphi报告了很多内存泄漏。搜索,所以我发现了以下问题,解释为什么这不起作用:对象的引用计数

Why aren't descendants of TInterfacedObject garbage collected?

有提出有一种解决方法,但它需要我(至少如果我得到它的权利)编写自定义界面IFileInfo并为它提供了许多我想避免的获得者和制定者。

编辑我应该补充说我将创建FileInfo对象插入到两种不同类型的散列表中:一种从TBucketList降序,另一种是Codegear论坛的散列映射实现。在内部,它们都是用户指针,所以情况与其他问题一样。

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

回答

5

不幸的是,Delphi编译器生成必要的代码来公司仅当您使用的接口(在你的情况自定义接口IFileInfo)/ DEC引用计数。此外,如果接口被转换为指针(或TObject),同样不可能引用计数。例如,assumming全局变量列表:从TList:

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

该方法返回列表[list.Count - 1]之后将包含悬挂指针。

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

2

此功能提供给接口,但不提供给对象。

您可以创建类似的东西,但你需要重写一些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 
+0

感谢您的详细解答!虽然我不完全理解。我仍然需要调用TRefCountObject.Free的权利?或者我如何使用它? – jpfollenius 2009-04-23 10:42:32

+0

增加了一些使用信息。 – 2009-04-23 10:56:34

+0

所以我仍然必须确保每个对象至少调用一次Free,对吧?并没有办法避免这种情况? – jpfollenius 2009-04-23 11:00:35

0

对此有一个很长的解释,但总而言之:从TInterfacedObject继承(而不是自己调用Free)是不够的,您需要使用对象工厂动态为您创建对象,并使用接口 - 指向任何地方的对象,而不仅仅是对象引用变量。 (是的,这意味着你不能只是切换'旧代码'而不看过去)

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; 
8

Delphi中的引用计数只适用于仅通过接口引用实例的情况。只要你混合接口引用和类引用,那么你就有麻烦了。

基本上你想要引用计数,而不需要创建一个接口,其中定义了所有的方法和属性。有三种方法可以做到这一点,而这些方法大致按我推荐的顺序进行。

  1. 巴里凯利写了一篇关于Smart Pointers的文章。它使用Delphi 2009中的泛型,但我相当肯定,如果您还没有使用2009,那么您可以将它硬编码到您正在使用的特定版本类型(它是一个很好的版本)。

  2. 另一种适用于Delphi的更多版本和较少修改的方法是Janez Atmapuri Makovsek的value type wrapper。它是为TStringList实现的一个例子,但你可以适应任何类型。

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

这是关于德尔福的事情,有一种自由的方式来完成事情。在我看来,选项#1是最好的选择 - 如果可以,请使用Delphi 2009。

祝你好运!

1

要添加到已经说过的内容,如果要存储对接口的引用,而不是使用TList,请使用TInterfaceList。参考计数将一直工作。

3

如果你想消除在TObject实例上释放的调用,那么你可能想看看原生Delphi的垃圾收集器。我知道有两种不同的垃圾收集器和一种垃圾收集技术,每种都有优点和缺点。

其中一个可能会为你工作。