Back Original

Modern Object Pascal Introduction for Programmers

4.1. Basics

We have classes. At the basic level, a class is just a container for

  • fields (which is fancy name for "a variable inside a class"),

  • methods (which is fancy name for "a procedure or function inside a class"),

  • and properties (which is a fancy syntax for something that looks like a field, but is in fact a pair of methods to get and set something; more in Properties).

  • Actually, there are more possibilities, described in More stuff inside classes and nested classes.

type
  TMyClass = class
    MyInt: Integer; 
    property MyIntProperty: Integer read MyInt write MyInt; 
    procedure MyMethod; 
  end;

procedure TMyClass.MyMethod;
begin
  WriteLn(MyInt + 10);
end;

4.2. Inheritance, virtual methods, override, reintroduce

We have inheritance and virtual methods.

In the example below, class TMyClassDescendant inherits from class TMyClass. The TMyClassDescendant is a descendant of TMyClass, and TMyClass is an ancestor of TMyClassDescendant.

program MyProgram;

{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

uses
  SysUtils;

type
  TMyClass = class
    MyInt: Integer;
    procedure MyVirtualMethod; virtual;
  end;

  TMyClassDescendant = class(TMyClass)
    procedure MyVirtualMethod; override;
  end;

procedure TMyClass.MyVirtualMethod;
begin
  WriteLn('TMyClass shows MyInt + 10: ', MyInt + 10);
end;

procedure TMyClassDescendant.MyVirtualMethod;
begin
  WriteLn('TMyClassDescendant shows MyInt + 20: ', MyInt + 20);
end;

var
  C: TMyClass;
begin
  C := TMyClass.Create;
  try
    C.MyVirtualMethod;
  finally
    FreeAndNil(C);
  end;

  C := TMyClassDescendant.Create;
  try
    C.MyVirtualMethod;
  finally
    FreeAndNil(C);
  end;
end.

When a method is virtual it means that the compiler searches for the method implementation at runtime, based on the actual class of the instance. What does this mean in practice?

  • Run the above example unmodified. Note that the method MyVirtualMethod is virtual. The call C.MyVirtualMethod selects the appropriate implementation based on the actual class of the instance C. When C is of class TMyClassDescendant, the TMyClassDescendant.MyVirtualMethod implementation is called. Thus the output should be:

    TMyClass shows MyInt + 10: 10
    TMyClassDescendant shows MyInt + 20: 20
  • Now modify the above example removing the virtual; and override; pieces. Both calls C.MyVirtualMethod will now call the implementation from TMyClass, because C is declared as TMyClass, so at compile-time all the compiler knows is that C is a TMyClass. The output will be:

    TMyClass shows MyInt + 10: 10
    TMyClass shows MyInt + 10: 20

    In short, this is usually not what you want. You want virtual methods.

By default methods are not virtual, declare them with virtual to make them so. Overrides must be marked with override, otherwise you will get a warning. To hide a method (declared in ancestor as virtual) without overriding it (usually you don’t want to do this, unless you know what you’re doing) use reintroduce.

4.3. Classes and class instances, constructors, destructors

Example in the section above shows a class called TMyClass (and another class called TMyClassDescendant). The class is a type, you can also think of it as a template. The class itself doesn’t hold any values — there is no memory reserved for the field MyInt: Integer declared in the example above.

Note

It is actually possible for a class to "hold values" by using class variables, but for now let’s forget about this possibility. Focus on simple classes that have only regular fields.

To reserve memory for the fields, we need to create a class instance.

Creating the class instance is done by invoking a constructor.

  • Constructor is a special kind of a method, using the keyword constructor.

  • Before invoking a constructor, a memory for the class instance is allocated, and then the constructor code is called.

  • You don’t need to define a constructor in all your classes. All classes implicitly descend from the TObject which has a parameter-less constructor called Create. So you always have a constructor, even if you didn’t define one.

  • But you can define a constructor in your class. It’s the best way to initialize a class instance. If you want to later depend that e.g. "initial value of field X is Y", then make it so (X := Y;) in the constructor.

  • Your own constructors are usually also called just Create. More details about naming constructors and destructors are in The virtual destructor called Destroy.

You invoke the constructor, allocating a class instance, like this:

You define your own constructor like this:

type
  TMyClass = class
  public
    X: Integer;
    constructor Create;
  end;

constructor TMyClass.Create;
begin
  inherited Create; 
  
  X := 123;
end;

Conversely, when a class is destroyed, a destructor is called.

  • It is again a special kind of a method, using the keyword destructor.

  • After invoking the destructor, a memory for the class instance is released. Accessing the fields of the destroyed instance is no longer allowed.

  • Again, you don’t need to define a destructor in all your classes. All classes implicitly descend from the TObject which has a parameter-less destructor called Destroy.

  • But you can define a destructor in your class. This is your last chance to do any "cleanup". E.g. maybe your class instance created some other class instances, internal, and now they need to be freed.

  • If you define one, there should be only one destructor, called Destroy, always with override;. More details why it should be so are in The virtual destructor called Destroy.

Here’s an example:

{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

uses
  SysUtils;

type
  TMyClass = class
  private
    InternalStuff: TObject;
  public
    constructor Create;
    destructor Destroy; override;
  end;

constructor TMyClass.Create;
begin
  inherited Create; 
  InternalStuff := TObject.Create;
  Writeln('TMyClass.Create');
end;

destructor TMyClass.Destroy;
begin
  Writeln('TMyClass.Destroy');
  FreeAndNil(InternalStuff); 
  inherited Destroy; 
end;

var
  C: TMyClass;
begin
  C := TMyClass.Create;
  try
    
  finally
    FreeAndNil(C); 
  end;
end.

4.4. Testing class (is), typecasting (as, TMyClass(X))

To test the class of an instance at runtime, use the is operator. To typecast the instance to a specific class, use the as operator.

program is_as;

{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

uses
  SysUtils;

type
  TMyClass = class
    procedure MyMethod;
  end;

  TMyClassDescendant = class(TMyClass)
    procedure MyMethodInDescendant;
  end;

procedure TMyClass.MyMethod;
begin
  WriteLn('MyMethod');
end;

procedure TMyClassDescendant.MyMethodInDescendant;
begin
  WriteLn('MyMethodInDescendant');
end;

var
  Descendant: TMyClassDescendant;
  C: TMyClass;
begin
  Descendant := TMyClassDescendant.Create;
  try
    Descendant.MyMethod;
    Descendant.MyMethodInDescendant;

    
    C := Descendant;
    C.MyMethod;

    
    
    if C is TMyClassDescendant then
      (C as TMyClassDescendant).MyMethodInDescendant;

  finally
    FreeAndNil(Descendant);
  end;
end.

Instead of casting using X as TMyClass, you can also use the unchecked typecast TMyClass(X). This is faster, but results in an undefined behavior if the X is not, in fact, a TMyClass descendant. So don’t use the TMyClass(X) typecast, or use it only in a code where it’s blindingly obvious that it’s correct, for example right after testing with is:

if A is TMyClass then
  (A as TMyClass).CallSomeMethodOfMyClass;

if A is TMyClass then
  TMyClass(A).CallSomeMethodOfMyClass;

4.5. Properties

Properties are a very nice "syntactic sugar" to

  1. Make something that looks like a field (can be read and set) but underneath is realized by calling a getter and setter methods. The typical usage is to perform some side-effect (e.g. redraw the screen) each time some value changes.

  2. Make something that looks like a field, but is read-only. In effect, it’s like a constant or a parameter-less function.

type
  TWebPage = class
  private
    FURL: string;
    FColor: TColor;
    function SetColor(const Value: TColor);
  public
    
    property URL: string read FURL;
    procedure Load(const AnURL: string);
    property Color: TColor read FColor write SetColor;
  end;

procedure TWebPage.Load(const AnURL: string);
begin
  FURL := AnURL;
  NetworkingComponent.LoadWebPage(AnURL);
end;

function TWebPage.SetColor(const Value: TColor);
begin
  if FColor <> Value then
  begin
    FColor := Value;
    
    Repaint;
    
    
    
    RenderingComponent.Color := Value;
  end;
end;

Note that instead of specifying a method, you can also specify a field (typically a private field) to directly get or set. In the example above, the Color property uses a setter method SetColor. But for getting the value, the Color property refers directly to the private field FColor. Directly referring to a field is faster than implementing trivial getter or setter methods (faster for you, and faster at execution).

When declaring a property you specify:

  1. Whether it can be read, and how (by directly reading a field, or by using a "getter" method).

  2. And, in a similar manner, whether it can be set, and how (by directly writing to a designated field, or by calling a "setter" method).

The compiler checks that the types and parameters of indicated fields and methods match with the property type. For example, to read an Integer property you have to either provide an Integer field, or a parameter-less method that returns an Integer.

Technically, for the compiler, the "getter" and "setter" methods are just normal methods and they can do absolutely anything (including side-effects or randomization). But it’s a good convention to design properties to behave more-or-less like fields:

  • The getter function should have no visible side-effects (e.g. it should not read some input from file / keyboard). It should be deterministic (no randomization, not even pseudo-randomization :). Reading a property many times should be valid, and return the same value, if nothing changed in-between.

    Note that it’s OK for getter to have some invisible side-effect, for example to cache a value of some calculation (known to produce the same results for given instance), to return it faster next time. This is in fact one of the cool possibilities of a "getter" function.

  • The setter function should always set the requested value, such that calling the getter yields it back. Do not reject invalid values silently in the "setter" (raise an exception if you must). Do not convert or scale the requested value. The idea is that after MyClass.MyProperty := 123; the programmer can expect that MyClass.MyProperty = 123.

  • The read-only properties are often used to make some field read-only from the outside. Again, the good convention is to make it behave like a constant, at least constant for this object instance with this state. The value of the property should not change unexpectedly. Make it a function, not a property, if using it has a side effect or returns something random.

  • The "backing" field of a property is almost always private, since the idea of a property is to encapsulate all outside access to it.

  • It’s technically possible to make set-only properties, but I have not yet seen a good example of such thing:)

Note

Properties can also be defined outside of class, at a unit level. They serve an analogous purpose then: look like a global variable, but are backed by a getter and setter routines.

4.5.1. Serialization of properties

Published properties are the basis of a serialization (also known as streaming components) in Pascal. Serialization means that the instance data is recorded into a stream (like a file), from which it can be later restored.

Serialization is what happens when Lazarus reads (or writes) the component state from an xxx.lfm file. (In Delphi, the equivalent file has .dfm extension.) You can also use this mechanism explicitly, using routines like ReadComponentFromTextStream from the LResources unit. You can also use other serialization algorithms, e.g. FpJsonRtti unit (serializing to JSON).

In the Castle Game Engine: Use the CastleComponentSerialize unit (based on FpJsonRtti) to serialize our user-interface and transformation component hierarchies.

At each property, you can declare some additional things that will be helpful for any serialization algorithm:

  • You can specify the property default value (using the default keyword). Note that you are still required to initialize the property in the constructor to this exact default value (it is not done automatically). The default declaration is merely an information to the serialization algorithm: "when the constructor finishes, the given property has the given value".

  • Whether the property should be stored at all (using the stored keyword).

4.6. Exceptions - Quick Example

We have exceptions. They can be caught with try …​ except …​ end clauses, and we have finally sections like try …​ finally …​ end.

{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

program MyProgram;

uses
  SysUtils;

type
  TMyClass = class
    procedure MyMethod;
  end;

procedure TMyClass.MyMethod;
begin
  if Random > 0.5 then
    raise Exception.Create('Raising an exception!');
end;

var
  C: TMyClass;
begin
  Randomize;
  C := TMyClass.Create;
  try
    C.MyMethod;
  finally
    FreeAndNil(C);
  end;
end.

Note that the finally clause is executed even if you exit the block using the Exit (from function / procedure / method) or Break or Continue (from loop body).

See the Exceptions chapter for more in-depth description of exceptions.

4.7. Visibility specifiers

As in most object-oriented languages, we have visibility specifiers to hide fields / methods / properties.

The basic visibility levels are:

public

everyone can access it, including the code in other units.

private

only accessible in this class.

protected

only accessible in this class and descendants.

The explanation of private and protected visibility above is not precisely true. The code in the same unit can overcome their limits, and access the private and protected stuff freely. Sometimes this is a nice feature, allows you to implement tightly-connected classes. Use strict private or strict protected to secure your classes more tightly. See the Private and strict private.

By default, if you don’t specify the visibility, then the visibility of declared stuff is public. The exception is for classes compiled with {$M+}, or descendants of classes compiled with {$M+}, which includes all descendants of TPersistent, which also includes all descendants of TComponent (since TComponent descends from TPersistent). For them, the default visibility specifier is published, which is like public, but in addition the streaming system knows to handle this.

Not every field and property type is allowed in the published section (not every type can be streamed, and only classes can be streamed from simple fields). Just use public if you don’t care about streaming but want something available to all users.

4.8. Default ancestor

If you don’t declare the ancestor type, every class inherits from TObject.

4.9. Self

The special keyword Self can be used within the class implementation to explicitly refer to your own instance. It is equivalent to this from C++, Java and similar languages.

4.10. Calling inherited method

Within a method implementation, if you call another method, then by default you call the method of your own class. In the example code below, TMyClass2.MyOtherMethod calls MyMethod, which ends up calling TMyClass2.MyMethod.

{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

uses SysUtils;

type
  TMyClass1 = class
    procedure MyMethod;
  end;

  TMyClass2 = class(TMyClass1)
    procedure MyMethod;
    procedure MyOtherMethod;
  end;

procedure TMyClass1.MyMethod;
begin
  Writeln('TMyClass1.MyMethod');
end;

procedure TMyClass2.MyMethod;
begin
  Writeln('TMyClass2.MyMethod');
end;

procedure TMyClass2.MyOtherMethod;
begin
  MyMethod; 
end;

var
  C: TMyClass2;
begin
  C := TMyClass2.Create;
  try
    C.MyOtherMethod;
  finally FreeAndNil(C) end;
end.

If the method is not defined in a given class, then it calls the method of an ancestor class. In effect, when you call MyMethod on an instance of TMyClass2, then

  • The compiler looks for TMyClass2.MyMethod.

  • If not found, it looks for TMyClass1.MyMethod.

  • If not found, it looks for TObject.MyMethod.

  • if not found, then the compilation fails.

You can test it by commenting out the TMyClass2.MyMethod definition in the example above. In effect, TMyClass1.MyMethod will be called by TMyClass2.MyOtherMethod.

Sometimes, you don’t want to call the method of your own class. You want to call the method of an ancestor (or ancestor’s ancestor, and so on). To do this, add the keyword inherited before the call to MyMethod, like this:

This way you force the compiler to start searching from an ancestor class. In our example, it means that compiler is searching for MyMethod inside TMyClass1.MyMethod, then TObject.MyMethod, and then gives up. It does not even consider using the implementation of TMyClass2.MyMethod.

Tip

Go ahead, change the implementation of TMyClass2.MyOtherMethod above to use inherited MyMethod, and see the difference in the output.

The inherited call is often used to call the ancestor method of the same name. This way the descendants can enhance the ancestors (keeping the ancestor functionality, instead of replacing the ancestor functionality). Like in the example below.

{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

uses SysUtils;

type
  TMyClass1 = class
    constructor Create;
    procedure MyMethod(const A: Integer);
  end;

  TMyClass2 = class(TMyClass1)
    constructor Create;
    procedure MyMethod(const A: Integer);
  end;

constructor TMyClass1.Create;
begin
  inherited Create; 
  Writeln('TMyClass1.Create');
end;

procedure TMyClass1.MyMethod(const A: Integer);
begin
  Writeln('TMyClass1.MyMethod ', A);
end;

constructor TMyClass2.Create;
begin
  inherited Create; 
  Writeln('TMyClass2.Create');
end;

procedure TMyClass2.MyMethod(const A: Integer);
begin
  inherited MyMethod(A); 
  Writeln('TMyClass2.MyMethod ', A);
end;

var
  C: TMyClass2;
begin
  C := TMyClass2.Create;
  try
    C.MyMethod(123);
  finally FreeAndNil(C) end;
end.

Since using inherited to call a method with the same name, with the same arguments, is a very common case, there is a special shortcut for it: you can just write inherited; (inherited keyword followed immediately by a semicolon, instead of a method name). This means "call an inherited method with the same name, passing it the same arguments as the current method".

Tip

In the above example, all the inherited …​; calls could be replaced by a simple inherited;.

Note 1: The inherited; is really just a shortcut for calling the ancestor’s method with the same variables passed in. If you have modified your own parameter (which is possible, if the parameter is not const), then the ancestor’s method can receive different input values from your descendant. Consider this:

procedure TMyClass2.MyMethod(A: Integer);
begin
  WriteLn('TMyClass2.MyMethod beginning ', A);
  A := 456;
  
  inherited;
  WriteLn('TMyClass2.MyMethod ending ', A);
end;

Note 2: You usually want to make the MyMethod virtual when many classes (along the "inheritance chain") define it. More about the virtual methods in the section below. But the inherited keyword works regardless of whether the method is virtual or not. The inherited always means that the compiler starts searching for the method in an ancestor, and it makes sense for both virtual and non-virtual methods.

4.11. Virtual methods, override and reintroduce

By default, the methods are not virtual. This is similar to C++, and unlike Java.

When a method is not virtual, the compiler determines which method to call based on the currently declared class type, not based on the actually created class type. The difference seems subtle, but it’s important when your variable is declared to have a class like TFruit, but it may be in fact a descendant class like TApple.

The idea of the object-oriented programming is that the descendant class is always as good as the ancestor, so the compiler allows to use a descendant class always when the ancestor is expected. When your method is not virtual, this can have undesired consequences. Consider the example below:

{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

uses SysUtils;

type
  TFruit = class
    procedure Eat;
  end;

  TApple = class(TFruit)
    procedure Eat;
  end;

procedure TFruit.Eat;
begin
  Writeln('Eating a fruit');
end;

procedure TApple.Eat;
begin
  Writeln('Eating an apple');
end;

procedure DoSomethingWithAFruit(const Fruit: TFruit);
begin
  Writeln('We have a fruit with class ', Fruit.ClassName);
  Writeln('We eat it:');
  Fruit.Eat;
end;

var
  Apple: TApple; 
begin
  Apple := TApple.Create;
  try
    DoSomethingWithAFruit(Apple);
  finally FreeAndNil(Apple) end;
end.

This example will print

We have a fruit with class TApple
We eat it:
Eating a fruit

In effect, the call Fruit.Eat called the TFruit.Eat implementation, and nothing calls the TApple.Eat implementation.

If you think about how the compiler works, this is natural: when you wrote the Fruit.Eat, the Fruit variable was declared to hold a class TFruit. So the compiler was searching for the method called Eat within the TFruit class. If the TFruit class would not contain such method, the compiler would search within an ancestor (TObject in this case). But the compiler cannot search within descendants (like TApple), as it doesn’t know whether the actual class of Fruit is TApple, TFruit, or some other TFruit descendant (like a TOrange, not shown in the example above).

In other words, the method to be called is determined at compile-time.

Using the virtual methods changes this behavior. If the Eat method would be virtual (an example of it is shown below), then the actual implementation to be called is determined at runtime. If the Fruit variable will hold an instance of the class TApple (even if it’s declared as TFruit), then the Eat method will be searched within the TApple class first.

In Object Pascal, to define a method as virtual, you need to

  • Mark its first definition (in the top-most ancestor) with the virtual keyword.

  • Mark all the other definitions (in the descendants) with the override keyword. All the overridden versions must have exactly the same parameters (and return the same types, in case of functions).

{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

uses SysUtils;

type
  TFruit = class
    procedure Eat; virtual;
  end;

  TApple = class(TFruit)
    procedure Eat; override;
  end;

procedure TFruit.Eat;
begin
  Writeln('Eating a fruit');
end;

procedure TApple.Eat;
begin
  Writeln('Eating an apple');
end;

procedure DoSomethingWithAFruit(const Fruit: TFruit);
begin
  Writeln('We have a fruit with class ', Fruit.ClassName);
  Writeln('We eat it:');
  Fruit.Eat;
end;

var
  Apple: TApple; 
begin
  Apple := TApple.Create;
  try
    DoSomethingWithAFruit(Apple);
  finally FreeAndNil(Apple) end;
end.

This example will print

We have a fruit with class TApple
We eat it:
Eating an apple

Internally, virtual methods work by having so-called virtual method table associated with each class. This table is a list of pointers to the implementations of virtual methods for this class. When calling the Eat method, the compiler looks into a virtual method table associated with the actual class of Fruit, and uses a pointer to the Eat implementation stored there.

If you don’t use the override keyword, the compiler will warn you that you’re hiding (obscuring) the virtual method of an ancestor with a non-virtual definition. If you’re sure that this is what you want, you can add a reintroduce keyword. But in most cases, you will rather want to keep the method virtual, and add the override keyword, thus making sure that it’s always invoked correctly.