Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reference-counting for objects

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?

like image 652
jpfollenius Avatar asked Apr 23 '09 10:04

jpfollenius


3 Answers

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!

like image 138
Jim McKeeth Avatar answered Oct 29 '22 22:10

Jim McKeeth


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.

like image 35
Kcats Avatar answered Oct 29 '22 21:10

Kcats


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;
like image 28
Steffen Binas Avatar answered Oct 29 '22 22:10

Steffen Binas