2009-04-23 50 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的垃圾收集器。我知道有兩種不同的垃圾收集器和一種垃圾收集技術,每種都有優點和缺點。

其中一個可能會爲你工作。