Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Dr.Bob's Delphi Notes Dr.Bob's Delphi Clinics Dr.Bob's Delphi Courseware Manuals
 Dr.Bob Examines... #2
See Also: other Dr.Bob Examines columns or Delphi articles

Delphi Class Methods
Apart from headaches, people often acquire a number of abilities and tricks. But even for a wizard, it's always a question whether or not we're using the full potential of our abilities...

This time, I'd like to examine a special construct in the Object Pascal language that is fairly seldom used. I'm talking about class methods; methods that don't belong to an instance of a class, but to the class itself (i.e. without having access or even needing an instance in the first place). Such methods can implement meaning or behaviour which is specific to the entire class, for example a reference counter or an error routine (not dependent on instance data, for example for a list or array of data), an implementation version number or about box, etc.
At the highest level, class methods are used by TObject to store the ClassName and ClassType, among many others. Of course, no matter how many instances we ever have, the ClassName will never change, so in those cases it's often a good idea to at least consider using a class method.

Class Instance Reference Counters
As an example, let's consider the situation where we'd need to maintain the number of instances of a given class; also called the reference counter. The Object Pascal syntax definition for a set of class methods to function as a reference counter is as follows:

  type
    TBobComponent = class(TComponent)
    private
      class procedure AddInstance;
      class procedure ReleaseInstance;
    public
      class function NumberOfInstances: Integer;
    end;
The "class" prefix must be repeated when we implement these methods as well, so the implementation section contains the following three routines:
  class procedure TBobComponent.AddInstance;
  class procedure TBobComponent.ReleaseInstance;
  class function TBobComponent.NumberOfInstances: Integer;
Now, in order to be able to store the reference counter for the specific TBobComponent class, we would ideally use a class field. Like a static member variable in C++. Unfortunately, such a thing doesn't exist, so we must hide the placeholder as an implementation detail not of the class, but of the unit that contains the class definition (and hence also implementation). In short, the implementation section for our unit can contain the following:
  implementation
  var
    TBobComponent_Instances: LongInt = 0;

  class procedure TBobComponent.AddInstance;
  begin
    Inc(TBobComponent_Instances)
  end;

  class procedure TBobComponent.ReleaseInstance;
  begin
    Dec(TBobComponent_Instances)
  end;

  class function TBobComponent.NumberOfInstances: Integer;
  begin
    Result := TBobComponent_Instances
  end;
We now have one public class method, and two private class methods (that can only be called by the class itself). In order to indeed use the class methods, we should include a call to AddInstance inside each of the constructors (one is called for every instance), and a call to ReleaseInstance inside the Destroy destructor:
  constructor TBobComponent.Create(AOwner: TComponent);
  begin
    inherited;
    AddInstance
  end;

  destructor TBobComponent.Destroy;
  begin
    ReleaseInstance;
    inherited
  end;
Since the reference counter variable TBobComponent_Instances is initialised to zero (inside the implementation section of our unit, so safe for outside "lurkers"), we can only use the public function NumberOfInstances to see how many instances exist at a give time. And this number may be zero, indicating that no instance exists. Also, in the finalization section of the unit, we can check the value of TBobComponent.NumberOfInstances, and if it's not zero we probably have a memory leak somewhere (so you could and probably should report this in some way):
  initialization
    // Note: we cannot have a finalization section without an
    // initialization section first - even if it's empty
  finalization
    if TBobComponent.NumberOfInstances <> 0 then
      MessageBox(HWnd(0),
        PChar(Format('Warning: %d TBobComponents not cleaned up!',
          [TBobComponent.NumberOfInstances])),
          'ATComp.Finalization', mb_OK)
  end.
Note the call here: we can use the classtype (TBobComponent) as prefix: we don't need nor even want an instance, and can just call the class method directly. And in this case, since the finalization section is obviously in the same unit, we can even call the private class methods AddInstance and ReleaseInstance (for whatever value that would add to this example).

Keeping track of your class instances can alert you to memory leaks in order to prevent headaches. And Delphi class methods are just what you need to implement them - or should I say just what the doctor ordered...?


This webpage © 2000-2010 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.