Tutorial :Reference-counting for objects



Question:

In my code I use a small data-storing class, which is created in different places. To avoid memory leaks and simplify things, I want to use reference counting, so I did

type TFileInfo = class (TInterfacedObject, IInterface)  

and removed all my manual calls to TFileInfo.Free. Unfortunately Delphi reported a lot of memory leaks. Searching on SO I found the following question explaining why this doesn't work:

Why aren't descendants of TInterfacedObject garbage collected?

There is a workaround presented there but it requires me (at least if i get it right) to write a custom interface IFileInfo and provide it with a lot of getters and setters, which I want to avoid.

EDIT I should add that I insert the create FileInfo objects into two different kinds of hash tables: one descending from TBucketList and another one is a hash map implementation from the Codegear forum. Internally they both user pointers, so the situation is just like in the other question.

Is there any other possibility to make objects in Delphi use reference-counting?


Solution:1

Unfortunately, the Delphi compiler generates the necessary code to inc/dec reference count only when you use interfaces (in your case custom interface IFileInfo). Moreover, if interfaces are cast to pointer (or TObject for that matter), again no reference counting is possible. For example, assumming global variable list : TList:

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

after the method returns list[list.Count - 1] will contain dangling pointer.

So interfaces cannot be used in a hashmap that casts them to pointers, the hashmap implementation must keep them as IInterface.


Solution:2

The reference counting in Delphi only works if you only have a reference to your instance via an interface. As soon as you mix interface references and class references then you are in trouble.

Essentially you want reference counting without the need to create an interface with all the methods and properties defined in it. There are three ways to do this, and these are roughly in the order I would recommend them.

  1. Barry Kelly wrote a post about Smart Pointers. It uses the Generics in Delphi 2009, but I am pretty sure you could hard code it to the specific versions of type you are using if you are not using 2009 yet (it is a great release BTW).

  2. Another way that works with more versions of Delphi and less modification is the value type wrapper by Janez Atmapuri Makovsek. It is an example implemented for TStringList, but you could adapt it for any type.

  3. The third way is to create a interfaced pointer (similar to Barry's Smart Pointer, but not so smart). I believe there is one in JCL, but I don't remember the details for sure. Basically this is an interface that accepts a TObject reference at construction. Then when it's reference count reaches zero it calls free on the object you passed to it. This method really only works for short lived instances that you are no passing as parameters because you separate the reference counted reference from the actually used reference. I would recommend one of the other two methods instead, but if you prefer this method and want more information just let me know.

That is the thing about Delphi, there are a free ways of accomplishing things. Option #1 is the best one in my opinion - Get Delphi 2009 and use that method if you can.

Good luck!


Solution:3

If you want to eliminate calls to free on TObject instances then you might want to look at a garbage collector for native Delphi. I am aware of 2 different garbage collectors and a garbage collecting technique, each with pros and cons.

One of those will probably work for you.


Solution:4

Don't mix object references and interface references.

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;  


Solution:5

This functionality is supplied for interfaces but not for objects.

You can create something like it, but you need to override some of the structure of 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;  

You need RefCountedCopy to assign the object to another variable. But then you have a refcounted object.

How to use this:

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  


Solution:6

To add to what has already been said, if you want to store references to Interfaces, instead of using a TList, use a TInterfaceList. The ref count will work consistently.


Solution:7

There's a long explanation to this, but in short: Inheriting from TInterfacedObject (and not calling Free yourself), is not enough, you need to use an object-factory-dynamic to create the objects for you, and use interface-pointers to the object everywhere, not just object-reference-variables. (Yes, that means you can't just switch 'old code' without looking it over)


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »