views:

262

answers:

5

Hi all,

In the project I am working on, there are cases where more than one TList objects contain the same item object.

Essentially, there is a master list which contains all the item objects, then smaller lists that contain only a subset of all the items. The items are the same however, not a copy.

Where the problem arises is during destruction. The main list is freed, which frees all of the items. The main list takes care of freeing the items, by overriding "notify", and the sub-lists override the "Notify" event so that the item is not freed a second time - which would fail anyway.

However, when using FastMM4, the memory leak log lists the items as leaking memory.

So how to go about freeing objects that belong to 2 or more lists?

By the way, this is not my code, I'm just doing some light maintenance on it. I'd like to avoid having to create a clone of each object to put in the separate lists if I can, but hey, a man's gotta do what a man's gotta do :o)

Thanks,

Bourgui

EDIT

Nevermind, I must be crazy. Now FastMM4 doesn't flag the items as leaking... Only the sub-lists, which are actuallya sub-class of the main list. There must be something I'm missing here. I'm going to run more tests to get a clearer picture of what is going on.

Thanks for all the replies so far.

+1  A: 

It has been 6+ years since I wrote any Delphi, but in general I think you want to do something like this:

  • As you said, the main list frees all of the items when it itself is freed;
  • The sublists do not attempt to free the items, since they are freed by the destruction of the main list. Rather, they simply remove the item from their own list, knowing that the destruction is being handled elsewhere.

That's rather succinct, but I think it wraps up the general pattern you want.

JMD
That's actually what's happening. I'm not sure why, but I was getting flags in FastMM that I'm not getting anymore.Cheers!
Bourgui
+4  A: 

Why do the sublists override the notify events? A TList does not free the items it contains. Or are you talking about TObjectLists? If TList, are you sure the items are actually freed? Is there any code that does it? If not, they are not freed and fastmm is right: They leak.

dummzeuch
Agreed. If it's tObjectLists make the main list own the objects, make the sublists not own the objects. Everything should clean up fine with no games. If they're tLists you'll need to free them all before killing the main list.
Loren Pechtel
Oops, typed too fast. I forgot to mention that the TList is also modified so that the items are freed. I updated the main description.
Bourgui
A: 

You can try to use automatic reference counting mechanism for interfaces in Delphi. This way the objects would free themselves as soon as the last reference is cleared and you don't have to free them explicitly. To do this the objects contained in the list need to inherit from TInterfacedObject and to implement some interface.

Max
A: 

This is very inneficient, but maybe eficiency is not your first goal here, so what I should do is to sub-class the TList, creating a particular set for this one which tracks all instances of TSharedList and, when one item is deleted in one list, search on all the others lists and delete the item there too.

Something like this (not thread safe):

unit SharedLists;

interface
uses Classes;

type
  TSharedList = class(TList)
  protected
    procedure Notify(Ptr: Pointer; Action: TListNotification); override;
  public
    constructor Create;
    destructor Destroy; override;
  end;

implementation

var
  SharedListTracker: TList;
  RecursiveCallFlag: Boolean;

procedure TSharedList.Notify(Ptr: Pointer; Action: TListNotification);
var
  I: Integer;
begin
  if RecursiveCallFlag then
    Exit;
  RecursiveCallFlag := True;
  try
    if Action = lnDeleted then
      for I := 0 to SharedListTracker.Count - 1 do
        if (TSharedList(SharedListTracker[I]) <> Self) and (TSharedList(SharedListTracker[I]).IndexOf(Ptr) <> -1) then
          TSharedList(SharedListTracker[I].Remove(Ptr);
  finally
    RecursiveCallControl := False;
  end;
end;

constructor TSharedList.Create;
begin
  inherited Create;
  SharedListTracker.Add(Self);
end;

destructor TSharedList.Destroy;
begin
  SharedListTracker.Remove(Self);
  inherited;
end;

initialization
  SharedListTracker := TList.Create;
finalization
  SharedListTracker.Free;
end.

I'm pretty sure it doesn't compile (never tried), but it shows my idea.

jachguate
Thanks, I understand what you're getting at. It apears to be working now however, so I don't think I'll need to try it.
Bourgui
+1  A: 

@dummzeuch is correct. TLists don't free contained items.

If the lists are indeed TObjectLists, then TObjectList has the concept of ownership, and the default is that objects added to a list ARE owned by the list, and freed when the list is destroyed.

TObjectList has an OwnsObjects parameters which is true, but there is an overloaded Constructor which takes a parameter which can be set to False so that contained objects are not owned by the list.

In your scenario, the main list would have the default value for the OwnsObjects parameter, and the secondary list would be constructed so that they don't own the contained objects.

Conor Boyd
I've actually never looked at TObjectList. This sounds interesting, so I'll go and learn more about it. But in any case, I can't seem to replicate the initial issue, so I'll leave this be for now. THanks though!
Bourgui