Which memory management techniques exist? - delphi

I know of these three techniques:
Manually
uses
System.Classes;
procedure DoSomething;
var
sl: TStringList;
begin
sl := TStringList.Create;
try
finally
sl.Free; // Invoking destructor
end;
end;
Reference counting / interface (TInterfacedObject)
Fastest example from the top of my head in the standard library:
uses
Xml.XMLDoc, Xml.XMLIntf;
procedure DoSomething;
var
xmldoc: IXMLDocument;
begin
xmldoc := TXMLDocument.Create(nil) as IXMLDocument;
end; // No reference to xmldoc anymore, freed automatically
Ownership
Like almost the whole VCLibrary or this one:
uses
System.Generics.Collections;
procedure DoSomething;
var
ol: TObjectList<TObject>;
i: Integer;
o: TObject;
begin
ol := TObjectList<TObject>.Create(true); // the list takes ownership of the objects
try
for i := 0 to 9 do begin
o := TObject.Create;
ol.Add(o);
end;
finally
ol.Free; // does not only free the list but all objects in the list, too
end;
end;
Are there more?

When it comes to memory management models for managing object instances in Delphi, there are two: manual memory management and automatic reference counting. All object instances will be released either manually or through reference counting mechanism.
But when it comes to actual coding patterns, there is number of ways we can write the code in order to trigger the release of an object instance and it is almost impossible to list and categorize them all.
The best way to illustrate the complexity involved is by asking additional question:
What do you consider as a memory management technique?
For instance, manually releasing object instance requires invoking the destructor. But there are several commonly used ways to do so: by calling Free, Destroy, or FreeAndNil. But, Free and FreeAndNil eventually will call Destroy. So the question is, should we consider that those different methods of invoking the destructor are the same technique or different techniques? What about other custom written methods that will trigger destruction of an object instance?
When it comes to releasing reference counted object instance, your example has shown indirect way of release - just letting reference go out of scope. But there is an additional way to release such object instance, and that is by explicitly assigning nil to such reference.
procedure DoSomething;
var
xmldoc: IXMLDocument;
begin
xmldoc := TXMLDocument.Create(nil) as IXMLDocument;
...
xmldoc := nil;
...
end;
Again, the question is whether we consider those two different examples as the same or different?
When it comes to ownership, it is just a way to delegate releasing an object instance to some other entity. At the end, in case of manually managed object instances some code at some point will directly invoke destructor on such object. While this is clearly a different coding pattern than directly invoking destructor on object reference without going through additional layers of indirection, at the end the instance will be released manually.
We can also transfer ownership of reference counted object instances. If you have a collection that holds interface references, then this can also be considered as ownership transfer, as release of those instances will depend on the release of the collection itself, even though involved code will not directly call destructor, but will rely on automatic reference counting to do so.
The next question that arises is: What about fields? Your first example shows construction and destruction of local object instance. If we have an object field in a class and manually call Free to such field in its destructor, should we consider that as a manual technique or ownership transfer, because actual release of that inner object instance will depend on the release of its outer, owning object.
There is additional aspect to reference counting. While compiler automatically inserts reference counting code (calls to _AddRef and _Release methods) in appropriate places, the _Release method itself will have to directly call the destructor to actually free the instance. In a way this is just another example of ownership transfer, with some help of the compiler.
From one perspective, we can say that those three techniques you have mentioned are the (two) three basic techniques to release an object instance. But one the other hand, there is an infinite number of them.

Related

Is this a bug in System.Net.HttpClient on Rio?

This is the function found in Delphi Rio in System.Net.HttpClient
THTTPClientHelper = class helper for THTTPClient
....
procedure THTTPClientHelper.SetExt(const Value);
var
{$IFDEF AUTOREFCOUNT}
LRelease: Boolean;
{$ENDIF}
LExt: THTTPClientExt;
begin
if FHTTPClientList = nil then
Exit;
TMonitor.Enter(FHTTPClientList);
try
{$IFDEF AUTOREFCOUNT}
LRelease := not FHTTPClientList.ContainsKey(Self);
{$ENDIF}
LExt := THTTPClientExt(Value);
FHTTPClientList.AddOrSetValue(Self, LExt);
{$IFDEF AUTOREFCOUNT}
if LRelease then __ObjRelease;
{$ENDIF}
finally
TMonitor.Exit(FHTTPClientList);
end;
end;
What the guy try to do with LRelease here?
{$IFDEF AUTOREFCOUNT}
LRelease := not FHTTPClientList.ContainsKey(Self);
{$ENDIF}
LExt := THTTPClientExt(Value);
FHTTPClientList.AddOrSetValue(Self, LExt);
{$IFDEF AUTOREFCOUNT}
if LRelease then __ObjRelease;
{$ENDIF}
So if FHTTPClientList doesn't contain the THTTPClient add it in the FHTTPClientList and then reduce it's refcount by one. Why reduce it's refcount by one?? the THTTPClient is still alive and used why breaking it's refcount? Their is a bug here, maybe the guy make a typo, but i don't understand what he want to do originally ...
for info this this how items are removing from the dictionary :
procedure THTTPClientHelper.RemoveExt;
begin
if FHTTPClientList = nil then
Exit;
TMonitor.Enter(FHTTPClientList);
try
FHTTPClientList.Remove(Self);
finally
TMonitor.Exit(FHTTPClientList);
end;
end;
Purpose of the manual reference counting for ARC compiler in above code is simulating dictionary with weak references. Delphi generic collections are backed with generic arrays that will hold strong reference to any object added to the collection on ARC compiler.
There is several ways to achieve weak references - using pointers, using wrappers around object where object is declared as weak and manual reference counting in appropriate places.
With pointers you lose type safety, wrappers require significantly more code, so I guess the author of above code opted for manual reference counting. Nothing wrong with that part.
However, as you noticed, there is something fishy in that code - while SetExt routine is written properly RemoveExt has a bug resulting in a crash later on.
Let's go through the code in context on ARC compiler (I will omit compiler directives and unrelated code for brevity):
Since adding object into collection (array) increases reference count, to achieve weak reference we have to decrease reference count of the added object instance - that way the instance's reference count will remain the same after it is stored in collection. Next, when we remove object from such collection, we have to restore reference count balance and increase reference count. Also we have to make sure that object will be removed from such collection before it is destroyed - good place to do that is destructor.
Adding to collection:
LRelease := not FHTTPClientList.ContainsKey(Self);
FHTTPClientList.AddOrSetValue(Self, LExt);
if LRelease then __ObjRelease;
We add the object to the collection and then after collection holds strong reference to our object we can release it's reference count. If object is already inside collection that means it's reference count was already decreased and we must not decrease it again - that is the purpose of LRelease flag.
Removing from collection:
if FHTTPClientList.ContainsKey(Self) then
begin
__ObjAddRef;
FHTTPClientList.Remove(Self);
end;
If object is in collection we have to restore the balance and increase the reference count before removing object from collection. This is the part that is missing from RemoveExt method.
Making sure that object is not in the list upon destruction:
destructor THTTPClient.Destroy;
begin
RemoveExt;
inherited;
end;
Note: In order for such faked weak collection to work properly items must be added and removed only through above methods that take care of balancing reference count. Using any other original collection methods like Clear will result in broken reference count.
Bug or not?
In System.Net.HttpClient code broken RemoveExt method is called only in destructor, also FHTTPClientList is private variable and it is not altered in any other way. On the first glimpse, that code works properly, but actually contains rather subtle bug.
To unravel the real bug we need to cover possible usage scenarios, starting with few established facts:
Only methods that alter content and by that reference count of items in FHTTPClientList dictionary are SetExt and RemoveExt methods
SetExt method is correct
Broken RemoveExt method that does not call __ObjAddRef is called only in THTTPClient destructor and this is where this subtle bug originates.
When destructor is called upon any particular object instance that means object instance has reached it's lifetime, and any subsequent reference counting triggers (during destructor execution) have no influence on the code correctness.
That is ensured by applying objDestroyingFlag on FRefCount variable changing its value and any further count increasing/decreasing can no longer result in special value 0 that starts destruction process - so object is safe and will not get destroyed twice.
In above code when THTTPClient destructor is called that means last strong reference to object instance has gone out of scope or was set to nil and at that moment the only remaining live reference that can trigger reference counting mechanism is the one in FHTTPClientList. That reference has been cleared by RemoveExt method (broken or not) at that point as previously said it does not matter. And everything works fine.
But, author of the code has forgotten one tiny weeny thingy - DisposeOf method that triggers the destructor, but at that point object instance has not reached its reference counting lifetime. In other words - if destructor is called by DisposeOf, any subsequent reference counting triggers must be balanced because there are still live references to the object that will trigger reference counting mechanism after the destructor chain calls are completed. If we break the counting at that point result will be catastrophic.
Since THTTPClient is not TComponent descendant that requires DisposeOf it is easy to make the oversight and forget that someone, somewhere could call DipsoseOf on such variable anyway - for instance if you make owned list of THTTPClient instances clearing such list will call DisposeOf on them and happily break their reference count because RemoveExt method is ultimately broken.
Conclusion: Yes, it is a BUG.

Memory management of interfaces in Delphi

I am struggling to learn delphi and memory management, coming from C#.
The current incarnation of that struggle is that I don't know the right way to dispose of the objects when I am done with them. From reading and my experiments it seems that if I have an object that is cast as an interface, then my ONLY choice is set the reference to nil.
If I go an call
FreeAndNil()
I end up getting an access violation, EX:
var
foo: IFoo;
begin
foo := TFoo.Create();
FreeandNil(foo);
end;
Sure, all I need to do it change that foo:IFoo; to foo:TFoo; and it is happy. OR simply set the pointer to nil, NOT call freeandNil.
foo := nil;
So, on one level, I don't understand in the least where the AV is.
On a differently level, I want to write the code such that it does not need to know if it is an interface or an object. I want to be able to write all of my memory management the same exact way, but I can't seem to write a method that can deal with something that is a Class or an interface. Well, that is not true, I do have something, but it is so ugly I hesitate to post it.
But I guess I should also be asking, what is everyone else doing? Mentally keeping track of what is an interface and just nil those pointers? otherwise calling FreeAndNil?
I am going to want to implement things the first time around as a concrete class, but later come back and change that to an interface when I find some way that the code can do from 2 different ways. And I am not going to want to go through the code and change how it was dealing with that reference, that is the last thing on my mind at that point.
But for the sake of discussion, the best (almost only) idea I have is this class:
interface
type
TMemory = class(TObject)
class procedure Free(item: TObject); overload; static;
class procedure Free<T: IInterface>(item: T); overload; static;
end;
implementation
uses
System.SysUtils;
{ TMemory }
class procedure TMemory.Free(item: TObject);
begin
FreeandNil(item);
end;
class procedure TMemory.Free<T>(item: T);
begin
//don't do anything, it is up the caller to always nil after calling.
end;
Then I can consistently call:
TMemory.Free(Thing);
Thing := nil;
Test code:
procedure TDoSomething.MyWorker;
var
foo: IFoo;
fooAsClass: TFoo;
JustAnObject: TObject;
begin
foo := TFoo.Create();
fooAsClass := TFoo.Create();
JustAnObject := TObject.Create();
TMemory.Free(foo);
foo := nil;
TMemory.Free(fooAsClass);
fooAsClass := nil;
TMemory.Free(JustAnObject);
JustAnObject := nil;
end;
runs with no leaks or access violations. (using MadExcept)
But a big thank you to the Delphi community on SO. You guys have been the best thing out there for learning!
The reason for the access violation is that FreeAndNil takes an untyped parameter, but expects it to be an object. So the method operates on the object.
procedure FreeAndNil(var Obj);
var
Temp: TObject;
begin
Temp := TObject(Obj); //Obj must be a TObject otherwise all bets are off
Pointer(Obj) := nil; //Will throw an AV if memory violation is detected
Temp.Free; //Will throw an AV if memory violation is detected
end;
A memory violation in the above might (NB not guaranteed) be detected if you destroy an object that has either been previously destroyed or never created. It's also likely to be detected if Obj doesn't reference an object at all but something else (such as an interface, record, Integer because these don't implement Free and if they did, it wouldn't be located in the same way as TObject.Free).
On a differently level, I want to write the code such that it does not need to know if it is an interface or an object. I want to be able to write all of my memory management the same exact way.
This is like saying you want to use your car in exactly the same way that you use your shower.
Ok, maybe the difference is not quite that extreme. But the point is that interfaces and objects (and for that matter records) use different memory management paradigms. You cannot manage their memory in the same way.
Objects need to be explicitly destroyed. You can use an ownership model, but destruction is still an explicit external action.
Interfaces are reference counted. The compiler injects code to track the number of fields and variables referencing (looking at) the underlying instance. Typically the object destroys itself when the last reference is released. (There are ways beyond the scope of this answer to change this.)
If we access some object by interface variable, it doesn't always mean that object is destroyed the moment reference counter drops to zero. For example, TComponent methods _AddRef and _Release implementations are 'dummy': no reference counting is implemented and TComponent is never destroyed because interface variables are out of scope.
To behave as we expect from 'real' interfaces, all your objects should be descendants from TInterfacedObject or you need to implement _AddRef / _Release yourself.
Yes, there are 2 different approaches to memory management which usually co-exist in a program, but confusion (and AV) arises only when the same object is treated in both ways. If we destroyed object and only then the interface variables have gone out of scope, they call _Release method of destroyed object which causes access violation. That's some risky business, though with some attention it is doable.
Classic Delphi components are not reference-counted, the concept of ownership is used instead. Each component has an owner whose responsibility is to free all the memory when it itself is destroyed. So each component has an owner, but it may also have a lot of pointers to another components, like when Toolbar has ImageList variable. If such components were refcounted, they would never be destroyed because of circular reference, so in order to break this circle you'd need 'weak' references as well which don't 'count'. They are here, too, but that's very recent feature of Delphi.
If there is some hierarchy in your objects, so you know that 'bigger' objects need all of 'smaller' ones to function, then use this good old approach, it's pretty simple and has very good implementation in Delphi, which is: you can make a code which will be leak-free no matter where exception could arise. There are all these little things like using .Free instead of .Destroy, because if exception happened in constructor, destructor is called automatically, and so on. Very clever solution in fact.
I'd use refcounted interfaces only if you don't know for how long some object is needed for you and there is no suitable 'owner' for it. I did it with scanned image which I saved to file in one thread, while converting to smaller image to show on screen on another thread. When all is done, image is no more needed in RAM and can be destroyed, but I have no idea which happens first. In this case using refcounting is best thing to do.

Creating and destroying objects in Delphi

I am new to Delphi and am trying to better understand object creation / freeing as I am used to the luxury of .NET's GC. I have two questions specifically:
Let's assume I am setting a TDataSource as below. In .NET I wouldn't explicitly destroy the object as I am with adoQuery.Free. But I am assuming that with Delphi I need to free these objects. However, by destroying the adoQuery I am also setting the dataset to null. In this way adoQuery is meant to be a locally scoped variable to the function only with the datsource being retuned from the function. Therefore, how can I best handle this?
dataSrc := TDataSource.Create(nil);
dataSrc.DataSet := adoQuery;
dataSrc.Enabled := true;
{ adoQuery.Free; }
cnt := DataSrc.DataSet.RecordCount;
I've been reading several suggestions when returning a variable from a function that the best thing to do is create the variable within the caller and pass it to the subroutine. Therefore, the signature to a function would look like:
AdoConnectionManager.GetResult(query : String; dataSrc: TDataSource) : TDataSource;
Result := dataSrc;
This is unattractive to me. I'd prefer to have a new variable created within the subroutine and then returned back to the caller. However, this is something again I never really had to worry about with .NET GC and here I have to explicitly destroy the variable, right?
Thanks!
You've asked two questions. One concerns these database classes, and I'm going to ignore that question since I don't know anything about those classes. Instead I will answer the other questions. Do note that this sort of answer is why the site policy is for questions to be asked one at a time.
Regarding a function that returns a new object, that is certainly viable. However, it is sometimes more flexible to let the caller supply the object. That allows them to re-use instance, or supply objects that are derived from a base class. A classic example would be a function that populated a TStrings instance.
In this scenario you'd probably use a procedure rather than a function. It might look like this:
procedure PopulateList(List: TMyList);
If you want to have a function that returns a newly minted instance that would be done like so:
function CreateAndPopulateList: TMyList;
begin
Result := TMyList.Create;
try
// code to populate Result goes here, and may raise exceptions
except
Result.Free; // in case of exceptions, we must destroy the instance to avoid leaks
raise;
end;
end;
Note the naming. I use create to imply to the caller that a new instance is created. The calling code would look like this:
List := CreateAndPopulateList;
try
// do stuff with list
finally
List.Free;
end;
And this pattern is the standard object creation pattern. So you use CreateAndPopulateList just as you could a constructor.
It should also be mentioned here, that Delphi also provides Reference-Counting (but different to .NET).
The very short explanation to Reference-Counting in Delphi:
In difference to other Languagues, Reference-Counting in Delphi is only available by using Interfaces. Further, there is no Garbage-Collector: a reference-counted Object gets instantly destroyed when its Referencecount reaches 0.
So as an Delphi Developer, there are the following "global" Rules for destroying Instances:
- you do destroy an Object manually, whenever it's declared as a ClassType (e.g. var m: TMyClass)
- you never destroy an Object manually, whenever it's declared as a InterfaceType (e.g. var m: IMyClass)
With Delphi when you create an object, you should decide how it would be freed. There are several ways:
You can free it manually
It can be freed together with it's owner
It can be freed as a part of TObjectList or similar container
It can be freed because it's interfaced and reference counter for it became zero
And so on...
About first question: you should understand that with Delphi object variable is a pointer to object. When leaving function, you can lost locally scoped (pointer) variable, but you don't harm object itself. For example, you can do something like this:
function GetDataSource: TDataSource;
var Query: TADOQuery;
begin
Result := TDataSource.Create(nil);
Query := TADOQuery.Create(Result);
Query.SQL.Text := ' ... ';
Result.DataSet := Query;
end;
It'll give you datasource you want with background query. When you free this datasource, query also would be freed.
About second question: intrafunction creation of return object is a normal practise, part of good design. Yes, you should decide who will free this object and how. You can use many strategies, there are no silver bullet here. Just for example, you can decide to add parameter 'datasource's owner' to function above and controls it's lifetime this way.

Memory leaks in Delphi app. How to properly dispose objects and strings?

My question is about debugging memory leaks which seem to be a nightmare.
In my app there is a simple class derived from TObject. All objects of that class are stored in a collection/list of of the class derived from TObjectList:
type
TOffer = class(TObject)
Item: string;
Price: string;
Id: string;
end;
TOffers = class(TObjectList<TOffer>)
protected
procedure SetOffer(I: Integer; AOffer: TOffer);
function GetOffer(I: Integer): TOffer;
public
property Offers[I: Integer]: TOffer read GetOffer write SetOffer
end;
The usage scenario:The crawler downloads the offers, parses them and saves to objects collection. This approach seems to be quite convenient as I can refer to the objects later (fill grids/lists, write them to file, etc.)
The problem is the proper disposal of the objects to avoid memory leaks. The app allocates ~4Mb memory on start but after processing ~12k offers it devours 32Mb. The leaks caused by not properly disposed objects/variables after the process finishes.
ReportMemoryLeaksOnShutdown shows horrible digits, but the crucial is -- I have no idea where to look and how to properly debug the damn thing.
Another example is the variable var MyString: string which also needs a proper disposal!! It was sorta insight for me :) I thought each procedure/function automatically manages garbage collection of the out-of-scope variables.
The list of offers is created by a function:
function GetOffersList: TOffers;
begin
Result := TOffers.Create;
while not rs.EOF do
begin
Offer := TOffer.Create;
try
// here come collected offer attributes as variables of type string:
Order.Item := CollectedOfferItem;
Order.Price := CollectedOfferPrice;
Order.Id := CollectedOfferId;
Result.Add(Offer);
finally
Offer := nil;
end;
end;
end;
Then I address those offers directly as a collection. The key thing is that I want this app to run 24/7, so the correct resource disposal is a must.
How to properly dispose object(s) of the above types?
Shall I consider the other techniques to manage object/object lists?
How to properly dispose variables of type string?
Can you please advise the good reading on fighting memory leaks in Delphi?
Thank you.
By default, when you create an object, you become its owner. So long as you are the owner, you are responsible for freeing it. Here are some of the common patterns:
1. Local variable
For an object that is created in a method and only referred to locally, you use the try/finally pattern:
Obj := TMyClass.Create;
try
... use Obj
finally
Obj.Free;
end;
2. Object owned by another object
Commonly created in the constructor and destroyed in the destructor. Here you have a member field of the owning object that holds the reference to the owned object. All you need to do is call Free on all owned objects in the owning class destructor.
3. Owned TComponent
If a TComponent or a derived class is created with an Owner, then that owner destroys the component. You do not need to.
4. TObjectList or similar with OwnsObjects set to True
You show this pattern in your question. You create a TObjectList<T> and by default OwnsObjects is True. This means that when you add a member to the container, the container assumes ownership. From that point on the container assume responsibility for destroying its members and you do not have to. However, somebody still has to destroy the container.
5. Reference counted interfaced objects
Common examples are objects derived from TInterfacedObject. The interface reference counting manages lifetime. You don't need to destroy the object.
6. Function that creates and returns a new instance
This is towards the more tricky end of the spectrum. Thankfully it's a rather rarer pattern. The idea is that the function returns a newly instantiated and initialized object to the caller, who then assumes ownership. But while the function is still executing it is the owner and must defend against exceptions. Typically the code goes like this:
function CreateNewObject(...): TMyClass;
begin
Result := TMyClass.Create;
try
Result.Initialize(...);
except
Result.Free;
raise;
end;
end;
This has to be an exception handler with a call to Free and a re-raise because the code is not in a position to use a finally. The caller will do that:
Obj := CreateNewObject(...);
try
....
finally
Obj.Free;
end;
Looking at the code in the question, that appears to be using both items 4 and 6 from my list. However, do note that your implementation of GetOffersList is not exception safe. But there's no indication that is the problem. It seems plausible that the code that calls GetOffersList is failing to destroy up the container.
Why are you leaking strings? Well, strings are managed objects. They are referenced counted and you need to take no explicit action to destroy them. However, if they are contained in other classes, instances of which are leaked, the contained strings are also leaked. So concentrate on fixing the leaks of objects, and you'll take care of the string leaks.
For what it is worth, TOffer feels more like a value type than a reference type to me. It has no method and contains three simple scalar values. Why not make it a record and use TList<TOffer>?
So, how do you proceed? The FastMM leak report is what you need. You'll want the full FastMM rather than the cut down Embarcadero version. It will identify the allocations that were not matched with deallocations. Deal with them one by one.
In parallel with this, study good quality code. Good open source Delphi libraries will demonstrate all the patterns above, and many more. Learn from them.
String is auto-managed by the compiler, you do not need to free it manually (except in rare corner cases that do not apply to this situation). TObjectList has an OwnsObjects property that you can set to True so the list will free the objects automatically for you. Its constructor has an optional AOwnsObjects parameter to initialize the OwnsObjects property.

Delphi - How to free an object that belongs to 2 (or more) Lists

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.
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.
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.
#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.
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.
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.

Resources