5.5.2 Method invocation

Methods are called just as normal procedures are called, only they have an object instance identifier prepended to them (see also chapter 13, page 612). To determine which method is called, it is necessary to know the type of the method. We treat the different types in what follows.

Normal static methods

Normal (static) methods are methods that have been declared without a abstract or virtual keyword. When calling a static method, the declared (i.e. compile time) method of the object is used. For example, consider the following declarations:

Type  
  TParent = Object  
    ...  
    procedure Doit;  
    ...  
    end;  
  PParent = ^TParent;  
  TChild = Object(TParent)  
    ...  
    procedure Doit;  
    ...  
    end;  
  PChild = ^TChild;

As it is visible, both the parent and child objects have a method called Doit. Consider now the following declarations and calls:

Var  
  ParentA,ParentB : PParent;  
  Child           : PChild;  
 
begin  
   ParentA := New(PParent,Init);  
   ParentB := New(PChild,Init);  
   Child := New(PChild,Init);  
   ParentA^.Doit;  
   ParentB^.Doit;  
   Child^.Doit;

Of the three invocations of Doit, only the last one will call TChild.Doit, the other two calls will call TParent.Doit. This is because for static methods, the compiler determines at compile time which method should be called. Since ParentB is of type TParent, the compiler decides that it must be called with TParent.Doit, even though it will be created as a TChild. There may be times when the method that is actually called should depend on the actual type of the object at run-time. If so, the method cannot be a static method, but must be a virtual method.

Virtual methods

To remedy the situation in the previous section, virtual methods are created. This is simply done by appending the method declaration with the virtual modifier. The descendent object can then override the method with a new implementation by re-declaring the method (with the same parameter list) using the virtual keyword.

Going back to the previous example, consider the following alternative declaration:

Type  
  TParent = Object  
    ...  
    procedure Doit;virtual;  
    ...  
    end;  
  PParent = ^TParent;  
  TChild = Object(TParent)  
    ...  
    procedure Doit;virtual;  
    ...  
    end;  
  PChild = ^TChild;

As it is visible, both the parent and child objects have a method called Doit. Consider now the following declarations and calls :

Var  
  ParentA,ParentB : PParent;  
  Child           : PChild;  
 
begin  
   ParentA := New(PParent,Init);  
   ParentB := New(PChild,Init);  
   Child := New(PChild,Init);  
   ParentA^.Doit;  
   ParentB^.Doit;  
   Child^.Doit;

Now, different methods will be called, depending on the actual run-time type of the object. For ParentA, nothing changes, since it is created as a TParent instance. For Child, the situation also doesn’t change: it is again created as an instance of TChild.

For ParentB however, the situation does change: Even though it was declared as a TParent, it is created as an instance of TChild. Now, when the program runs, before calling Doit, the program checks what the actual type of ParentB is, and only then decides which method must be called. Seeing that ParentB is of type TChild, TChild.Doit will be called. The code for this run-time checking of the actual type of an object is inserted by the compiler at compile time.

The TChild.Doit is said to override the TParent.Doit. It is possible to acces the TParent.Doit from within the varTChild.Doit, with the inherited keyword:

Procedure TChild.Doit;  
begin  
  inherited Doit;  
  ...  
end;

In the above example, when TChild.Doit is called, the first thing it does is call TParent.Doit. The inherited keyword cannot be used in static methods, only on virtual methods.

To be able to do this, the compiler keeps - per object type - a table with virtual methods: the VMT (Virtual Method Table). This is simply a table with pointers to each of the virtual methods: each virtual method has its fixed location in this table (an index). The compiler uses this table to look up the actual method that must be used. When a descendent object overrides a method, the entry of the parent method is overwritten in the VMT. More information about the VMT can be found in Programmer’s Guide.

As remarked earlier, objects that have a VMT must be initialized with a constructor: the object variable must be initialized with a pointer to the VMT of the actual type that it was created with.

Abstract methods

An abstract method is a special kind of virtual method. A method that is declared abstract does not have an implementation for this method. It is up to inherited objects to override and implement this method.

From this it follows that a method can not be abstract if it is not virtual (this can be seen from the syntax diagram). A second consequence is that an instance of an object that has an abstract method cannot be created directly.

The reason is obvious: there is no method where the compiler could jump to ! A method that is declared abstract does not have an implementation for this method. It is up to inherited objects to override and implement this method. Continuing our example, take a look at this:

Type  
  TParent = Object  
    ...  
    procedure Doit;virtual;abstract;  
    ...  
    end;  
  PParent=^TParent;  
  TChild = Object(TParent)  
    ...  
    procedure Doit;virtual;  
    ...  
    end;  
  PChild = ^TChild;

As it is visible, both the parent and child objects have a method called Doit. Consider now the following declarations and calls :

Var  
  ParentA,ParentB : PParent;  
  Child           : PChild;  
 
begin  
   ParentA := New(PParent,Init);  
   ParentB := New(PChild,Init);  
   Child := New(PChild,Init);  
   ParentA^.Doit;  
   ParentB^.Doit;  
   Child^.Doit;

First of all, Line 3 will generate a compiler error, stating that one cannot generate instances of objects with abstract methods: The compiler has detected that PParent points to an object which has an abstract method. Commenting line 3 would allow compilation of the program.

Remark: If an abstract method is overridden, the parent method cannot be called with inherited, since there is no parent method; The compiler will detect this, and complain about it, like this:

testo.pp(32,3) Error: Abstract methods can’t be called directly

If, through some mechanism, an abstract method is called at run-time, then a run-time error will occur. (run-time error 211, to be precise)

Class or static methods

Class methods or methods declared with the static directive are methods that are global to the object type. When called, the implicit ’self’ pointer is not available. This means that normal methods cannot be called, and none of the fields of an object can be accessed. Class variables can be used, however.

Class or static methods are regular methods, they can be assigned to a procedural variable.

The following program demonstrates all this. The commented-out statements will not compile.

{$APPTYPE CONSOLE}  
{$IFDEF FPC}{$MODE DELPHI}{$H+}{$ENDIF}  
type  
  TTest = object  
    const Epsylon = 100;  
    var f : integer;  
    class var cv1,cv2:integer;  
    procedure myproc;  
    class procedure testproc;  
    class procedure testproc2;static;  
    procedure testproc3; static;  
  end;  
 
  procedure TTest.myproc;  
  begin  
    cv1:=0;  
    f:=1;  
  end;  
 
  class procedure TTest.Testproc;  
  begin  
    cv1:=1;  
    // f:=1;  
  end;  
 
  class procedure TTest.Testproc2;  
  begin  
    cv1:=2;  
    // f:=1;  
  end;  
 
  procedure TTest.Testproc3;  
  begin  
    cv1:=3;  
    // f:=1;  
  end;  
 
 
Var  
  P : Procedure;  
 
begin  
  P:=@TTest.Myproc;  
  P:=@TTest.Testproc;  
  P:=@TTest.Testproc2;  
  P:=@TTest.Testproc3;  
end.

Uncommenting one of the commented statements and trying to compile the resulting code will result in a compiler error:

ocv.pp(32,6) Error: Only class methods, class properties and  
  class variables can be accessed in class methods