11

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.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
alondono
  • 2,496
  • 2
  • 28
  • 34
  • 1
    Refactoring can help. – smooty86 Sep 15 '15 at 06:04
  • 1
    Your leaks are because your code is defective. We cannot see your code. In any case why subclass at all. You can use `TObjectList` directly. Unless you want to add methods to it. – David Heffernan Sep 15 '15 at 06:07
  • @DavidHeffernan I've edited the question adding a constructor and usage. That code, with `TObjectList`, doesn't leak. But if I change the `TMembers` declaration to be `TMembers = class(TObjectList)` now the code above shows memory leaks. And yes, I want to add methods to it. What I am showing here is just a simple made-up example for a, hopefully, clear question. – alondono Sep 15 '15 at 06:33
  • I still can not see the constructor which causes the memleaks. – Sir Rufo Sep 15 '15 at 06:40
  • No, you haven't added the code that has the leaks. Show a [mcve]. – David Heffernan Sep 15 '15 at 06:42
  • @SirRufo If you use `TMembers = class(TObjectList)` that very simple constructor up there causes the memory leaks. But if I remove the `` part, there are no memory leaks. – alondono Sep 15 '15 at 06:43
  • Show the defective code. Do **not** tell us a story about your code. – Sir Rufo Sep 15 '15 at 06:45
  • @SirRufo That up there is the defective code. Just to be sure, I copied it from here into a new project in Delphi. I noticed an error I had here before (I had PersonA := PersonA.Create;) but it is fixed already. And the code above doesn't have memory leaks, but as I've said twice before already, if you use `TMembers = class(TObjectList)` instead, you get memory leaks. – alondono Sep 15 '15 at 06:59
  • Well, now we we can answer your question :o) – Sir Rufo Sep 15 '15 at 07:01
  • 1
    I re-wrote your question to make it ask just what I believe you intended to ask, as per your comments. Also, with the question list this, it made it easier for me to answer!! ;-) – David Heffernan Sep 15 '15 at 07:06
  • @DavidHeffernan thank you for your edit. It it much clearer. Initially I didn't add the constructor because there was nothing in it, just `inherited`. Thanks again. – alondono Sep 15 '15 at 07:09
  • 1
    I my experience a simple complete program like this makes answering much easier, and it helps you too because the focus is clear and unobstructed. – David Heffernan Sep 15 '15 at 07:19

2 Answers2

14

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I have just tried the top solution and actually removing the `True` parameter also works. So, you only need `inherited Create;`. – alondono Sep 15 '15 at 07:11
  • 2
    But it's better to be explicit. My version of `TObjectList` forces the caller to pass OwnsObjects. – David Heffernan Sep 15 '15 at 07:15
1

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;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
JayDi
  • 1,037
  • 15
  • 24
  • You should describe **why** there are no memleaks. Just the code will not help to understand. – Sir Rufo Sep 15 '15 at 06:38
  • To enable memory leak report add `ReportMemoryLeaksOnShutdown := True;` in project source file before `Application.Initialize`. If there are memory leak then report will be shown after application close. – JayDi Sep 15 '15 at 06:47
  • That is how you proove there are no memleaks, but not **why**? – Sir Rufo Sep 15 '15 at 06:50
  • Because generic class `TObjectList` destroy all inner objects in that list automatically after `free` command. – JayDi Sep 15 '15 at 06:57
  • 1
    Well the OP inherit his class from `TObjectList` and he has memleaks. That is the question that needs an answer. – Sir Rufo Sep 15 '15 at 06:59
  • As I said in the question it is an old code using `TObjectList` that has been working fine without memory leaks, but when I tried changing it to use `TObjectList` instead, now I have memory leaks. I have tried various things like those in the question without success. – alondono Sep 15 '15 at 07:03
  • Because your original code was calling a `TObjectList` constructor that initializes `OwnsObjects` to true, whereas your `TObjectList` code is calling a constructor that initializes `OwnsObjects` to false instead. Thus the leak. David's answer explains in detail why this is happening in your new code. – Remy Lebeau Sep 15 '15 at 07:59
  • ReportMemoryLeaksOnShutdown can be set True at any time – David Heffernan Sep 16 '15 at 05:23