Why does this program report memory leaks?
{$APPTYPE CONSOLE}
uses
System.Generics.Collections;
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create;
end;
constructor TDerivedGenericObjectList.Create;
begin
inherited;
end;
var
List: TDerivedGenericObjectList;
begin
ReportMemoryLeaksOnShutdown := True;
List := TDerivedGenericObjectList.Create;
List.Add(TObject.Create);
List.Free;
end.
You are calling the parameterless constructor of TObjectList<T>
. That is in fact the constructor of TList<T>
, the class from which TObjectList<T>
is derived.
All the constructors declared in TObjectList<T>
accept a parameter named AOwnsObjects
which is used to initialise the OwnsObjects
property. Because you are bypassing that constructor, OwnsObjects
is defaulting to False
, and the members of the list are not being destroyed.
You should make sure that you call constructor of TObjectList<T>
that initialise OwnsObjects
. For instance:
{$APPTYPE CONSOLE}
uses
System.Generics.Collections;
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create;
end;
constructor TDerivedGenericObjectList.Create;
begin
inherited Create(True);
end;
var
List: TDerivedGenericObjectList;
begin
ReportMemoryLeaksOnShutdown := True;
List := TDerivedGenericObjectList.Create;
List.Add(TObject.Create);
List.Free;
end.
Perhaps a better variant would be to make your constructor also offer the AOwnsObjects
parameter:
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create(AOwnsObjects: Boolean = True);
end;
constructor TDerivedGenericObjectList.Create(AOwnsObjects: Boolean);
begin
inherited Create(AOwnsObjects);
end;
Or:
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create(AOwnsObjects: Boolean = True);
end;
constructor TDerivedGenericObjectList.Create(AOwnsObjects: Boolean);
begin
inherited;
end;
So, you might wonder why the original version picked out a TList<T>
constructor rather than one in TObjectList<T>
. Well, let's look at that in more detail. Here's your code again:
type
TDerivedGenericObjectList = class(TObjectList<TObject>)
public
constructor Create;
end;
constructor TDerivedGenericObjectList.Create;
begin
inherited;
end;
When inherited
is used in this way, the compiler looks for a constructor with the exact same signature as this one. It cannot find one in TObjectList<T>
because they all have parameter. It can find one in TList<T>
, and so that's the one that it uses.
As you mention in comments the following variant does not leak:
constructor TDerivedGenericObjectList.Create;
begin
inherited Create;
end;
This syntax, by contrast to the bare inherited
, will find methods that match when default parameters are substituted. And so the single parameter constructor of TObjectList<T>
is called.
The documentation has this information:
The reserved word inherited plays a special role in implementing polymorphic behavior. It can occur in method definitions, with or without an identifier after it.
If inherited is followed by the name of a member, it represents a normal method call or reference to a property or field, except that the search for the referenced member begins with the immediate ancestor of the enclosing method's class. For example, when:
inherited Create(...);
occurs in the definition of a method, it calls the inherited Create.
When inherited has no identifier after it, it refers to the inherited method with the same name as the enclosing method or, if the enclosing method is a message handler, to the inherited message handler for the same message. In this case, inherited takes no explicit parameters, but passes to the inherited method the same parameters with which the enclosing method was called. For example:
inherited;
occurs frequently in the implementation of constructors. It calls the inherited constructor with the same parameters that were passed to the descendant.
You can use generics. It's work fine without type casting and memory leak ( TObjectList<T>
or TObjectDictionary<T>
lists destroy inner objects automatic on free command).
Some tips:
TObjectList<TPerson>
-- destroy persons list automatic on free like membersList.Free
;
TList<TPerson>
-- do not destroy persons list. You must create destructor and free manually every person in list;
Here is example of your code (using new constructor, no memory leak and with backward compatibility with old code -- see GetPerson
):
type
TPerson = class
public
Name: string;
Age: Integer;
function Copy: TPerson;
end;
TMembers = class(TObjectList<TPerson>)
private
function GetPerson(i: Integer): TPerson;
public
property Person[i: Integer]: TPerson read GetPerson;
constructor Create(SourceList: TMembers); overload;
end;
{ TPerson }
function TPerson.Copy: TPerson;
var
person: TPerson;
begin
person := TPerson.Create;
person.Name := Self.Name;
person.Age := Self.Age;
Result := person;
end;
{ TMembers }
constructor TMembers.Create(SourceList: TMembers);
var
person: TPerson;
begin
inherited Create;
for person in SourceList do
begin
Self.Add(person.Copy);
end;
end;
function TMembers.GetPerson(i: Integer): TPerson;
begin
Result := Self[i];
end;
procedure TForm21.Button1Click(Sender: TObject);
var
person: TPerson;
memsList1: TMembers;
memsList2: TMembers;
begin
// test code
memsList1 := TMembers.Create;
person := TPerson.Create;
person.Name := 'name 1';
person.Age := 25;
memsList1.Add(person);
person := TPerson.Create;
person.Name := 'name 2';
person.Age := 27;
memsList1.Add(person);
memsList2 := TMembers.Create(memsList1);
ShowMessageFmt('mems 1 count = %d; mems 2 count = %d', [memsList1.Count, memsList2.Count]);
FreeAndNil(memsList1);
FreeAndNil(memsList2);
end;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With