Class Basics
In this section I’ll be covering many of the basics such as defining your own classes, the visibility directives, inheritance, properties and methods. We’ll start with a simple class definition and add to it as we go. So for this part, I’ve chosen a simple class that we can use to store a set of screen coordinates. Sounds simple enough, and yes, we already have TPoint, but TPoint isn’t a class its a record, so whats the difference?
A record is a collection of data. If you define a record type (lets use TPoint as an example) it looks like this:-
Code Fragment 1
001: TPoint = record
002: x : integer;
003: y : integer;
004: end;
As you can see, its pretty basic. It has two fields, X and Y. Both are integers. Thats about all we can say. The equivalent class declaration might
look something like this:-
Code Fragment 2
001: TMyPoint = class(TObject)
002: public
003: x : integer;
004: y : integer;
005: end;
Doesn’t appear to be much different, and functionally they are practically identical, with one major difference. When you declare a variable of type
TPoint, what you actually get is a variable that has space allocated for it on the heap and you can access the values it holds immediately, but with TMyPoint you get a pointer to an object, or more precisely a variable capable of pointing to an object of type TMyPoint. *PHEW*
So this difference… how does it affect me? Lets consider this example code:-
Code Fragment 3
001: var
002: pointVar : TPoint;
003: myPointVar : TMyPoint;
004: begin
005: pointVar.x:=0;
006: pointVar.y:=0;
007: myPointVar.x:=0;
008: myPointvar.y:=0;
In this example, we have declared two variables. pointVar which is of type TPoint and myPointVar which is of type TMyPoint. What the compiler does is to allocate memory to these variables as follows (NOTE:- This is theoretical allocation for example only, please don’t send me lots of emails telling me I’m wrong. The technicalities of allocating memory to objects will only confuse matters and won’t actually have much impact on this article):-
Memory map 1 (TPoint Vs. TMyPoint – Example Memory Allocation for Code fragment 3)
START OF HEAP
[pointVar.X (Byte 1 of 4)]
[pointVar.X (Byte 2 of 4)]
[pointVar.X (Byte 3 of 4)]
[pointVar.X (Byte 4 of 4)]
[pointVar.Y (Byte 1 of 4)]
[pointVar.Y (Byte 2 of 4)]
[pointVar.Y (Byte 3 of 4)]
[pointVar.Y (Byte 4 of 4)]
[myPointVar (Byte 1 of 4)]
[myPointVar (Byte 2 of 4)]
[myPointVar (Byte 3 of 4)]
[myPointVar (Byte 4 of 4)]
As you can see, the variable pointVar has a total of 8 bytes allocated to it (4 for its X component and 4 for its Y component). myPointVar on the other hand has only 4 bytes. The reason for this is that myPointVar does not store the data directly. It stores a pointer to an instance of the TMyPoint class (an instance of a class is normally called an object). If we now consider Code fragment 3, as execution progresses, we can see that we have space on the heap for pointVar.x and so line 5 will execute. We have space for pointVar.y and so line 6 will execute. It appears we have enough space for myPointVar.x, but this is where the major difference between records and classes comes into play.
A record variable contains the data. A variable holding a class is a pointer to another area of memory that holds the data. Consequently, what the compiler actually sees when you access a field of an object is a little something like this.
Compiler view 1 (Accessing TMyPoint.X)
myPointVar.X:=0;
becomes
myPointVar^.X:=0;
The compiler derefences the pointer for you and as a result, you will almost undoubtedly get an Access Violation at that point, because myPointVar could be holding any value and as a consequence could be pointing to memory allocated to a different application or even to memory that doesn’t physically exist.
To avoid this, you must first create an instance of your class as shown below.
Code fragment 4 (Creating an instance of TMyPoint)
001: var
002: pointVar : TPoint;
002: myPointVar : TMyPoint;
003: begin
004: myPointVar:=TMyPoint.create;
So what does this do to our memory map?
Memory map 2 (TPoint Vs. TMyPoint – Example Memory Allocation for Code fragment 4)
START OF HEAP
[pointVar.X (Byte 1 of 4)]
[pointVar.X (Byte 2 of 4)]
[pointVar.X (Byte 3 of 4)]
[pointVar.X (Byte 4 of 4)]
[pointVar.Y (Byte 1 of 4)]
[pointVar.Y (Byte 2 of 4)]
[pointVar.Y (Byte 3 of 4)]
[pointVar.Y (Byte 4 of 4)]
[myPointVar (Byte 1 of 4)] \
[myPointVar (Byte 2 of 4)] \__
[myPointVar (Byte 3 of 4)] / |
[myPointVar (Byte 4 of 4)] / |
-------------------------- |
|
SOMEWHERE ELSE |
-------------------------- |
[OBJECT - TMyPoint ] /___|
[ X (Byte 1 of 4) ] \
[ X (Byte 2 of 4) ]
[ X (Byte 3 of 4) ]
[ X (Byte 4 of 4) ]
[ Y (Byte 1 of 4) ]
[ Y (Byte 2 of 4) ]
[ Y (Byte 3 of 4) ]
[ Y (byte 4 of 4) ]
Ok, so this is super simplified, but I hope you get the picture. When you use a variable that is a record type, you actually get space allocated for you
and you can access it directly, with no hassles, but when you use a variable that is a class, you get a pointer that can point to an object, nothing more. It’s up to you to create an instance of the class yourself and allocate the value you get to the variable as shown in Code fragment 4.
So now you have an object you can use, get into the habit of cleaning it up after you. There is nothing more irritating than an application/process that gradually chews its way through megabytes of memory just because you forgot what the FREE method does
Recite these words and make them your mantra… Free is my friend and try..finally my ally! I know it’s cheesey, but if it works I’m happy, and you will be too when you’re not spending your time hunting memory leaks.
As an example, I’ll use TStringList, because it could in theory be holding a lot of data.
Code fragment 5 (Resource protection)
001: var
002: buffer : TStringList;
003: begin
004: buffer:=TStringList.create;
005: try
----
//////////////////
THE REST OF YOUR ROUTINE
//////////////////
----
006: finally
007: buffer.free;
008: end;
009: end;
Fairly straight forward piece of code, but the moment you add that ; and hit <ENTER> on line 4, you should immediately be thinking ahead to lines 5 through 8. Get into the habit of putting these in BEFORE you write the rest of your routine. I can’t emphasise this point enough. It will save you a lot of time later on.
Ok, so what have we learned so far? We’ve learned that a variable holding an object is a pointer to the object itself and that the compiler secretly derefences it for you. Also, that you need to create objects before you can use them and that for your own sanity, you need to free an object once you’ve finished with it, but we still haven’t covered why it’s better to use an object than a record.
In this example, it’s unlikely to be obviously apparent because it is so simple, but we’ll stick with it and introduce the concept of properties and methods.
Think of a property as a field of a record, but with a few more bells and whistles. When you declare a field in a record or an object as illustrated in
Code fragments 1 and 2, they are public and any process can read them and change them. Properties on the other hand allow you a little more control over what external processes can and cannot do with your data. Lets take a look.
Code fragment 6 (Property examples)
001: TMyPoint = class(TObject)
002: private
003: fX : integer;
004: fY : integer;
005: public
006: property x:integer read fX write fX;
007: property y:integer read fY write fY;
008: end;
This is looking more like a class definition and less like a record definition, so lets break it down.
TMyPoint = class(TObject)
When we declare a class, we need to know the name we want our new class declaration to have. In this case, “TMyPoint”. We also want to know which class is our parent. For this example I chose TObject.
private
One of the four key visibility directives. The “PRIVATE” directive tells the compiler that anything defined after it and before the next visibility directive
in that class should be private, that is, they should only be visible and consequently accessible to the class in which they are defined and any classes
also defined in the same unit. Now I don’t agree with the last section, nor do I condone using classes in this way as it goes totally against the grain of what OOP is, but it is there for some reason. So in our example, “fX” and “fY” are defined as being private and as such cannot be accessed outside of the TMyPoint class and the unit in which it is defined.
fX:integer;
fY:integer;
These two lines define the fields (hence the “f”) that contain the data we are planning on storing and operating on/using. Why are they private? Well, as stated above, we could make them public, but then we have no control over what happens when another process updates them (if we want to allow other processes/objects to update them at all). By making the fields that hold the data private we can make sure that the values cannot be changed directly.
public
Another of our visibility directives. The “PUBLIC” directive tells the compiler that anything defined after it and before the next visibility directive in the class can be seen and accessed by any process or object that has an instance of this class.
property x:integer read fX write fX;
property y:integer read fY write fY;
Here we can see our “properties”. Just like fields, they provide access to the data stored by our class, but unlike the basic field declarations of Code fragment 2, these tell us a whole lot more, so lets take a closer look.
A property declaration consists of at least 1 part. The name of the property. This much shortened version is normally used when working with descendant classes to alter the visibility of a particular named property. This use of property declarations will be dealt with in Part 2. A more typical property declaration goes like this:-
property <PROPERTYNAME>:<PROPERTYTYPE> [read <DATASOURCE>] [write <DATADESTINATION>] [default <DEFAULTVALUE>] [stored <TRUE|FALSE>];
Looks quite complex, but in reality, its quite straight forward. We provide the data type for the property with <PROPERTYTYPE>. This can be pretty much any standard data type, one thing you can’t do though is use array types as published properties (NOTE:- published does not mean public, we’ll cover the difference a little later).
So at this point, we know the property name and its data type. The next two items tell us where the data comes from when we read the property and where the data goes when we write to it. You’ll have noticed that these two components of the declaration are optional. By providing one, the other or both, you can declare a property as being read only (quite common), write only (not sure why you would want to do this, but it is possible so long as the property isn’t published) and read/write (most common). The next two items are mainly used when writing components. They may be discussed in a future article.
Now we know how to define properties, lets take a look at the properties in Code fragment 6. We have specified that our properties X and Y read data directly from and write data directly to the fields fX and fY respectively. So in essence we now have an object that operates like the TPoint record type. But we can do so much more.
Consider this… when you change the coordinates of TMyPoint, wouldn’t it be cool if it could calculate its distance to the coordinate origin… don’t know why you would want this, but for the sake of this example we do. This distance will be made accessible via a read only property called distanceToOrigin. To achieve this, we introduce the concept of property access methods. So, consider what we want to achieve.
When we change the coordinates, it calculates its distance to the origin.
So from this, we see that when we WRITE to the X and Y properties we want to perform some operations. This means we need two property access methods that will be executed when we write to the X and Y properties. Here is our class declaration.
Code fragment 7 (Property access methods I)
Interface Section (Type declaration)
----
001: TMyPoint = class(TObject)
002: private
003: fX : integer;
004: fY : integer;
005: fDistanceToOrigin : double;
006:
007: procedure setX(value:integer);
008: procedure setY(value:integer);
009: function distanceTo(aX,aY:integer):double;
010: public
011: constructor create;
012: property distanceToOrigin:double read fDistanceToOrigin;
013: property x:integer read fX write setX;
014: property y:integer read fY write setY;
015: end;
----
Implementation Section (Method implementation)
----
101: procedure TMyPoint.setX(value:integer);
102: begin
103: if (value<>fX) then
104: begin
105: fX:=value;
106: fDistanceToOrigin:=distanceTo(0,0);
107: end;
108: end;
109:
110: procedure TMyPoint.setY(value:integer);
111: begin
112: if (value<>fY) then
113: begin
114: fY:=value;
115: fDistanceToOrigin:=distanceTo(0,0);
116: end;
117: end;
118:
119: function TMyPoint.distanceTo(ax,ay:integer):double;
120: var
121: dx,dy : integer;
122: begin
123: dx:=round(abs(ax-fx));
124: dy:=round(abs(ay-fy));
125: result:=sqrt(sqr(dx)+sqr(dy));
126: end;
127:
128: constructor TMyPoint.create;
129: begin
130: inherited;
131: fX:=0;
132: fY:=0;
133: fDistanceToOrigin:=0.0;
134: end;
Looks complicated, but its actually pretty straight forward. So here goes takes deep breath
This class declaration handles a few things. When a new instance of our class is created, it is constructed. We may want to initialise its properties and get it ready for use. For this we have a pseudo event handler, the constructor “CREATE”. On line 11 we can see how it is declared, and on lines 128 through 134 we can see how it is implemented. When a new instance of the TMyPoint class is created, the create method is called. Since creating an object is a complex business, I’ve found it easiest to leave it to the experts by using the ‘inherited’ keyword to call the create method of the parent class (an inherited method) and then perform our own initialisation. An inherited method is the implementation of the method in the parent class (the class ours is descended from), and then its parent, and so on until we have run out of ancestors.
As you can see in our example, we simply use the ‘inherited’ keyword and then we initialise our properties so they make sense. ie. X = 0, Y = 0 and distanceToOrigin = 0. Moving on to the property declarations in lines 13 and 14. These translate to “read the value for the property from private variables fX and fY respectively and write the new value for the property to the property access methods setX and setY respectively”.
Data Flow 1 (Accessing properties X and Y)
Read [X] value is read from [fX]
Read [Y] value is read from [fY]
Write [X] sent to [setX] method saves this value to [fX]
Write [Y] sent to [setY] method saves this value to [fY]
The property access methods are themselves quite simple. They normally take a single parameter (we called this parameter ‘value’). This should be the same type as the property. I shall disect the two routines together since they are identical (with the obvious exception that setX access the X variables and setY accesses the Y variables.
Lets consider our routine (lines 101 to 108 and 110 to 117). It receives the new value for its property in the ‘value’ parameter. We could simply save this value in the private fields and then recalculate the distance to the origin, but I have included a simple if…then construct to check the existing value of the field (lines 103 and 112). By doing this we add a few additional instructions but we may save ourselves many many instructions by avoiding additional processing that isn’t needed. Once we have established that the new value is different to the existing value we save the new value to our field variables (lines 105 and 114) and then recalculate the value for the field fDistanceToOrigin on lines 106 and 115 (accessible via the distanceToOrigin property).
Now, depending on what you plan on doing with your object, this way of doing things may present unnecessary overheads. If you are constantly changing the values of X and Y and very rarely reading distanceToOrigin, then its operation could be greatly enhanced by reducing the overheads involved in setting X and Y. So, consider this version.
Code fragment 8 (Property access methods II)
Interface Section (Type declaration)
----
001: TMyPoint = class(TObject)
002: private
003: fX : integer;
004: fY : integer;
005:
006: function distanceTo(ax,ay:integer):double;
007: function getDistanceToOrigin:double;
008: public
009: constructor create;
010: property x:integer read fX write fX;
011: property y:integer read fY write fY;
012: property distanceToOrigin:double read getDistanceToOrigin;
013: end;
----
Implementation Section (Method implementation)
----
101: function TMyPoint.distanceTo(ax,ay:integer):double;
102: var
103: dx,dy : integer;
104: begin
105: dx:=round(abs(ax-fx));
106: dy:=round(abs(ay-fy));
107: result:=sqrt(sqr(dx)+sqr(dy));
108: end;
109:
110: constructor TMyPoint.create;
111: begin
112: inherited;
113: fX:=0;
114: fY:=0;
115: end;
116:
117: function TMyPoint.getDistanceToOrigin:double;
118: begin
119: result:=self.distanceTo(0,0);
120: end;
This is practically identical to version I, except instead of calculating the distance to the origin and storing it when we change X and Y, we now only calculate it when we read the property ‘distanceToOrigin’. At this point, the read method of the property is called. Read methods are functions that return a result type equivalent to the type of the property. How this result is generated is entirely up to you. But in our example, we simply call our private function ‘distanceTo’ with the parameters 0,0 representing the origin of our coordinate system.
By doing this, we only calculate it when we need it. So we have optimised our class to perform best under a clearly defined set of operating conditions (ie. X and Y are updated frequently and distanceToOrigin is rarely used). It may sound trivial, but it could make a huge difference.
Version I used a property write method (a procedure, the last parameter of which must be the same type as the property and that will receive the new value when the property is written to). Version II used a property read method (a function, the return value of which must be the same type as the property). By combining these we can create properties that use methods to read and write data.
We’ll cover property access methods some more a little later on, but I hope you are starting to get the picture. They represent one of the key reasons why I think it is better to use a class rather than a record. In the examples, the processing performed is extremely simple, but the sky really is the limit. If you created a class to hold user information for example. By utilising a property write method, you could load all the user data into your object when the ‘memberid’ property is written to. You could even have the object automagically save changes when key properties are updated (The users password maybe?).
That about wraps up class basics, apart from saying that other methods are implemented in exactly the same way as the property access methods already introduced.
Conclusion
This has ended up being somewhat larger than I intended, but looking back, I think it serves as a good introduction to class basics and some of the mechanics of classes. The next section I write on classes will be covering more class basics including the other key directives ‘protected’, ‘published’, ‘override’, ‘virtual’, ‘dynamic’, ‘abstract’ and ‘overload’ and we’ll look a little more at the ‘constructor’ and ‘destructor’ keywords. Hopefully, with this first part, I’ve clarified a few things for those of you who are new(ish) to Delphi and the other object pascal compilers and for those of you who aren’t so new, I hope you’ve picked up a snippet or two of useful info.
As I said at the start, I don’t consider myself a guru so I may have made some blinding errors or obvious ommissions. If you spot one, or you have questions or want a little help with classes, please drop me a mail (my address is in the introduction).
So, until next time, take care and thanks for reading.


