5

I implemented the following class:

type
  TUtilProcedure = procedure(var AJsonValue: TJSONObject);

  TCallback = class
  private
    FName: string;
    FProcedure: TUtilProcedure;
    FAnnotation: string;
  public
    constructor Create(AName: string; AProcedure: TUtilProcedure; AAnnotation: string); overload;
    constructor Create(ACallback: TCallback); overload;
    property Name: string read FName;
    property Proc: TUtilProcedure read FProcedure;
    property Annotation: string read FAnnotation;
 end;

Then I have a global variable:

procedures: TDictionary<string, TCallback>;

In OnFormActivate procedure I initalize the procedures variable:

procedures := TDictionary<string, TCallback>.Create();
procedures.Add('something', TCallback.Create('sth', @proc, 'annotation')); 
// ....

And then in OnFormClose I free it:

procedures.Clear;
procedures.Free;

Does my code leak memory? If so what is the correct way to free the dictionary? From what I know iteration is not good idea.

bob_saginowski
  • 1,429
  • 2
  • 20
  • 35

1 Answers1

12

The code leaks memory because the objects contained in a TDictionary are not automatically freed.

If you need to store objects in a dictionary, the adoption of a TObjectDictionary represents a better approach.

If you want the objects contained in the dictionary to be automatically freed, use the doOwnsValues flag while creating the instance of the collection.

  • When the variable is really global (i.e. is declared under var in the interface section of the unit), it should be created and destroyed in the initialization and finalization section of the unit itself.

    . . .
    var
      procedures: TObjectDictionary<string, TCallback>;
    . . .
    initialization
      procedures:= TObjectDictionary<string, TCallback>.Create([doOwnsValues]);
    finalization
      procedures.Free;
    
  • When your variable belongs to the form class itself, you should create the dictionary in the form's OnCreate event.

    . . .
    public
      procedures: TObjectDictionary<string, TCallback>;
    . . .
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      procedures:= TObjectDictionary<string, TCallback>.Create([doOwnsValues]);
    end;
    

    Free the dictionary in the form's OnDestroy event:

    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      procedures.Free;
    end;
    
  • Furthermore, if you want the variable belonging to the class to be accessed without the need of an instance of the class itself (this is referred as a static variable in many programming languages), you can declare the dictionary as a class var and optionally access it through a class property; in such a case, it's better to create and destroy the collection in the class constructor and in the class destructor.

    . . .
    TMyClass = class
      private
        class constructor Create;
        class destructor Destoy;
      public
        class var procedures: TObjectDictionary<string, TCallback>;
    end;
    . . .
    class constructor TMyClass.Create;
    begin
      procedures := TObjectDictionary<string, TCallback>.Create([doOwnsValues]);
    end;
    
    class destructor TMyClass.Destoy;
    begin
      procedures.Free;
    end;
    

TCallback = class
  private
    FName: string;
    FProcedure: TUtilProcedure;
    FAnnotation: string;
  public
    constructor Create(AName: string; AProcedure: TUtilProcedure; AAnnotation: string); overload;
    constructor Create(ACallback: TCallback); overload;
    property Name: string read FName;
    property Proc: TUtilProcedure read FProcedure;
    property Annotation: string read FAnnotation;
end;

As a side note, the TCallback class does not need to specify a destructor because only owns two strings and a pointer to a procedure. And so the default destructor inherited from TObject suffices.

fantaghirocco
  • 4,761
  • 6
  • 38
  • 48
  • That is fine for this case, where there is only one instance of the form. If there are more than instances of the form, using FormCreate and FormDestroy to construct or destroy such a variable may not be so good. But OTOH, using global variables is not recommendable anyway. – Rudy Velthuis Aug 26 '15 at 21:23
  • Hmmm... If the variable must really be global, for some reason or other, why not construct and destroy it in the initialization and finalization sections, respectively? – Rudy Velthuis Aug 26 '15 at 21:25
  • @Rudy Velthuis thank you for your comments: I have updated the answer according to your remarks; the multiple form instance issue was already pointed out by @David Heffernan, but I wrongly assumed too quickly that the *global* variable could have been a `private` one – fantaghirocco Aug 27 '15 at 09:15