|Delphi Clinic||C++Builder Gate||Training & Consultancy||Delphi Notes Weblog||Dr.Bob's Webshop|
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...?