Reference-counting for objects
Asked Answered
H

7

8

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?

Hydrophane answered 23/4, 2009 at 10:10 Comment(0)
G
6

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.

Giron answered 23/4, 2009 at 10:42 Comment(4)
But does the compiler generate the correct inc/dec at all if I insert all objects into a hash map?Hydrophane
This code does not compile. It is not possible to typecast interfaces to objects.Standifer
It does compile, but it doesn't work, and that was the point I tried to demonstrate. I have edited the answer again and I hope it is more clear now.Giron
Hard to choose the accepted answer, but I found yours the clearest description of what was the actual problem (the pointer based data structure)Hydrophane
C
8

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!

Clack answered 23/4, 2009 at 17:3 Comment(5)
I read your question again and I don't know that any of these answers work since your lists accept objects because they use pointers. These methods either use interfaces or records. Again, moving to Delphi 2009 provides generic containers that work with records, interfaces, objects or native types.Clack
While I think you have good points in your answer, none of the three ways does help with the problem that the OP has - none of them can be used in place of a TObject instance, to eliminate the need to call Free().Devastating
@JimMcKeeth: I'm using Delphi 2009, but it still lacks a generic and reasonably efficient hash map implementation, doesn't it?Hydrophane
Delphi 2009 has a TDictionary, but I don't know how efficient it is comparatively. Since it is generic then you could put things in it by interface reference instead of object reference.Clack
Although with Delphi 2009 you can use Barry Kelly's Smart Pointers with very little modification, and that might solve it for you too.Clack
G
6

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.

Giron answered 23/4, 2009 at 10:42 Comment(4)
But does the compiler generate the correct inc/dec at all if I insert all objects into a hash map?Hydrophane
This code does not compile. It is not possible to typecast interfaces to objects.Standifer
It does compile, but it doesn't work, and that was the point I tried to demonstrate. I have edited the answer again and I hope it is more clear now.Giron
Hard to choose the accepted answer, but I found yours the clearest description of what was the actual problem (the pointer based data structure)Hydrophane
U
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;
Undertint answered 23/4, 2009 at 14:37 Comment(0)
B
3

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
Babin answered 23/4, 2009 at 10:36 Comment(5)
Thanks for the detailed answer! I don't fully understand it though. I still have to call TRefCountObject.Free right? Or how do I use this?Hydrophane
So I still have to make sure to call Free at least once for each object, right? And no way to avoid this?Hydrophane
There's no silver bullet to this problem.Epilate
As I understand correctly, your problem is that you have one object referenced by two lists. Normally you declare one of these as the "owner" responsible for the destruction and the removal of other links. But even if you have a single list, you need to free each object. Or use a language with garbage collection.Babin
Okay then, I will go back to version which manually frees the memory for the objects. I was just curious if there is a way to "simulate" garbage collection for normal objects with the help of interfaces. As you and the other posters pointed out, there is not. Thanks for the valuable information!Hydrophane
C
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.

Clack answered 23/4, 2009 at 18:38 Comment(0)
F
1

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.

Flushing answered 23/4, 2009 at 17:14 Comment(1)
Problem is: I am in need of an efficient hash map implementation. The one I'm using (Barry Kelly posted it in the codegear forum IIRC) uses pointers so I can't work with interfaces unless I would have a hash map implementation that stores interfaces.Hydrophane
M
0

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)

Melton answered 23/4, 2009 at 11:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.