Virtual Methods
In Object Pascal, declaring a method with the VIRTUAL keyword and overriding it in a derived class with OVERRIDE enables polymorphism across the entire inheritance tree. Even when a variable is declared as the base class type, the method of the actual class is called at runtime, allowing each class to define its own behavior while sharing a common interface.
Syntax
{ -----------------------------------------------
Base class — declares virtual methods with VIRTUAL
----------------------------------------------- }
type
TBase = class(TObject)
public
{ virtual method — can be overridden in derived classes }
procedure Describe; virtual;
{ regular method — cannot be overridden in derived classes }
procedure Greet;
end;
{ -----------------------------------------------
Derived class — overrides the virtual method with OVERRIDE
----------------------------------------------- }
type
TChild = class(TBase)
public
{ OVERRIDE replaces the base class virtual method }
procedure Describe; override;
end;
{ -----------------------------------------------
Method implementations
----------------------------------------------- }
procedure TBase.Describe;
begin
WriteLn('TBase.Describe called.');
end;
procedure TBase.Greet;
begin
WriteLn('Hello (TBase.Greet)');
end;
procedure TChild.Describe;
begin
inherited Describe; { calls the base class implementation }
WriteLn('TChild.Describe called.');
end;
{ -----------------------------------------------
Polymorphism — using a base class variable to handle derived classes
----------------------------------------------- }
var
obj : TBase;
begin
obj := TChild.Create;
try
obj.Describe; { TChild.Describe is called at runtime }
finally
obj.Free;
end;
end.
Syntax Reference
| Syntax / Keyword | Description |
|---|---|
virtual | Declares a method as a virtual method in the base class. Derived classes can override it. |
override | Overrides a virtual method from the base class in a derived class. The signature (parameters and return type) must match the base class. |
inherited | Calls the base class method of the same name from within an overriding method. |
abstract | Used with virtual; abstract; to declare an abstract method. It has no implementation and must be overridden in all derived classes. |
class(TParentClass) | Declares a derived class that inherits from the specified class. Omitting it automatically inherits from TObject. |
inherited Create | Calls the parent class constructor in the constructor. Must always be called to correctly initialize the inheritance tree. |
| Dynamic dispatch | Methods declared with virtual / override are called based on the actual class type at runtime, not the declared type of the variable. |
| Static method | A method declared without virtual. The method of the variable's declared type is always called, and declaring a method with the same name in a derived class does not override the base class method. |
TObject | The top-level class that all Object Pascal classes implicitly inherit from. Provides basic methods such as Free and ClassName. |
Common Mistakes
Forgetting the override keyword causes method hiding
When redefining in a derived class a method that was declared virtual in the parent class, without override, polymorphism does not work and the parent class method is called instead. The compiler issues a warning — check for it.
type
TBase = class
procedure Describe; virtual;
end;
TChild = class(TBase)
procedure Describe; { override is missing — this is method hiding }
end;
var
obj : TBase;
begin
obj := TChild.Create;
try
obj.Describe; { TBase.Describe is called (not TChild.Describe) }
finally
obj.Free;
end;
end.
Adding override makes dynamic dispatch work correctly.
type
TChild = class(TBase)
procedure Describe; override; { with override, TChild.Describe is called }
end;
Directly instantiating an abstract class
Trying to Create a class that has abstract methods directly causes a runtime error. Always instantiate through a concrete subclass.
type
TEva = class
procedure Activate; virtual; abstract;
end;
var
e : TEva;
begin
e := TEva.Create; { runtime error: abstract class cannot be instantiated directly }
e.Activate;
e.Free;
end.
Create a concrete subclass and instantiate through it.
type
TEvaUnit01 = class(TEva)
procedure Activate; override;
end;
procedure TEvaUnit01.Activate;
begin
WriteLn('Unit-01 — Awakening mode activated.');
end;
var
e : TEva;
begin
e := TEvaUnit01.Create; { instantiate through the concrete subclass }
try
e.Activate;
finally
e.Free;
end;
end.
Sample Code
eva_override.pas
{$mode objfpc}
{ eva_override.pas — demonstrates inheritance and overriding with VIRTUAL / OVERRIDE }
{ Represents Neon Genesis Evangelion pilots and EVA units as a class hierarchy }
{
Compile and run:
fpc eva_override.pas && ./eva_override
}
program eva_override;
type
TEva = class(TObject)
private
FUnitNumber : Integer;
FPilot : string;
FSyncRate : Integer;
public
constructor Create(AUnit: Integer; const APilot: string; ASync: Integer);
destructor Destroy; override;
procedure PrintInfo; virtual;
procedure Activate; virtual; abstract;
procedure BoardingCheck;
property UnitNumber : Integer read FUnitNumber;
property Pilot : string read FPilot;
property SyncRate : Integer read FSyncRate write FSyncRate;
end;
type
TEvaUnit00 = class(TEva)
public
constructor Create;
procedure PrintInfo; override;
procedure Activate; override;
end;
type
TEvaUnit01 = class(TEva)
public
constructor Create;
procedure PrintInfo; override;
procedure Activate; override;
end;
type
TEvaUnit02 = class(TEva)
public
constructor Create;
procedure PrintInfo; override;
procedure Activate; override;
end;
constructor TEva.Create(AUnit: Integer; const APilot: string; ASync: Integer);
begin
inherited Create;
FUnitNumber := AUnit;
FPilot := APilot;
FSyncRate := ASync;
WriteLn('[Online] EVA Unit-', FUnitNumber, ' (Pilot: ', FPilot, ') connection confirmed.');
end;
destructor TEva.Destroy;
begin
WriteLn('[Offline] EVA Unit-', FUnitNumber, ' connection severed.');
inherited Destroy;
end;
procedure TEva.PrintInfo;
begin
WriteLn(' Unit : EVA Unit-', FUnitNumber);
WriteLn(' Pilot : ', FPilot);
WriteLn(' Sync Rate: ', FSyncRate, ' %');
end;
procedure TEva.BoardingCheck;
begin
WriteLn(' Boarding : ', FPilot, ' — entry plug boarding complete.');
end;
constructor TEvaUnit00.Create;
begin
inherited Create(0, 'Rei Ayanami', 60);
end;
procedure TEvaUnit00.PrintInfo;
begin
inherited PrintInfo;
WriteLn(' Type : Prototype');
WriteLn(' Armament : Positron Rifle — combat record confirmed.');
end;
procedure TEvaUnit00.Activate;
begin
WriteLn(' [Unit-00] Rei Ayanami — self-sacrifice AT Field deployment initiated.');
end;
constructor TEvaUnit01.Create;
begin
inherited Create(1, 'Shinji Ikari', 80);
end;
procedure TEvaUnit01.PrintInfo;
begin
inherited PrintInfo;
WriteLn(' Type : General-purpose humanoid decisive weapon (Test Type)');
WriteLn(' Ability : Awakening mode — defeats Angels autonomously in uncontrolled state.');
end;
procedure TEvaUnit01.Activate;
begin
WriteLn(' [Unit-01] Shinji Ikari — awakening mode activated, entering berserk state.');
end;
constructor TEvaUnit02.Create;
begin
inherited Create(2, 'Asuka Langley Soryu', 98);
end;
procedure TEvaUnit02.PrintInfo;
begin
inherited PrintInfo;
WriteLn(' Type : Mass-production prototype (combat deployment)');
WriteLn(' Ability : High-mobility close combat via ultra-high sync rate.');
end;
procedure TEvaUnit02.Activate;
begin
WriteLn(' [Unit-02] Asuka Langley Soryu — the best pilot fighting at full power!');
end;
var
evas : array[0..2] of TEva;
i : Integer;
begin
WriteLn('============================================================');
WriteLn(' NERV — EVA Activation Sequence Start');
WriteLn('============================================================');
WriteLn('');
evas[0] := TEvaUnit00.Create;
evas[1] := TEvaUnit01.Create;
evas[2] := TEvaUnit02.Create;
try
WriteLn('');
WriteLn('============================================================');
WriteLn(' Unit Status Check (virtual/override dynamic dispatch)');
WriteLn('============================================================');
for i := 0 to 2 do begin
WriteLn('');
evas[i].PrintInfo;
end;
WriteLn('');
WriteLn('============================================================');
WriteLn(' Boarding Check (regular method — always calls base class)');
WriteLn('============================================================');
WriteLn('');
for i := 0 to 2 do begin
evas[i].BoardingCheck;
end;
WriteLn('');
WriteLn('============================================================');
WriteLn(' Special Ability Activation (abstract virtual -> override)');
WriteLn('============================================================');
WriteLn('');
for i := 0 to 2 do begin
evas[i].Activate;
end;
WriteLn('');
WriteLn('============================================================');
WriteLn(' Sync Rate Update');
WriteLn('============================================================');
WriteLn('');
evas[0].SyncRate := 150;
evas[1].SyncRate := 400;
WriteLn(' Rei Ayanami sync rate updated to ', evas[0].SyncRate, ' %.');
WriteLn(' Shinji Ikari sync rate updated to ', evas[1].SyncRate, ' %.');
finally
WriteLn('');
WriteLn('============================================================');
WriteLn(' EVA Shutdown Sequence');
WriteLn('============================================================');
WriteLn('');
for i := 0 to 2 do begin
evas[i].Free;
end;
end;
WriteLn('');
WriteLn('============================================================');
WriteLn(' Sequence Complete');
WriteLn('============================================================');
end.
fpc eva_override.pas && ./eva_override Free Pascal Compiler version ... Linking ./eva_override ============================================================ NERV — EVA Activation Sequence Start ============================================================ [Online] EVA Unit-0 (Pilot: Rei Ayanami) connection confirmed. [Online] EVA Unit-1 (Pilot: Shinji Ikari) connection confirmed. [Online] EVA Unit-2 (Pilot: Asuka Langley Soryu) connection confirmed. ============================================================ Unit Status Check (virtual/override dynamic dispatch) ============================================================ Unit : EVA Unit-0 Pilot : Rei Ayanami Sync Rate: 60 % Type : Prototype Armament : Positron Rifle — combat record confirmed. Unit : EVA Unit-1 Pilot : Shinji Ikari Sync Rate: 80 % Type : General-purpose humanoid decisive weapon (Test Type) Ability : Awakening mode — defeats Angels autonomously in uncontrolled state. Unit : EVA Unit-2 Pilot : Asuka Langley Soryu Sync Rate: 98 % Type : Mass-production prototype (combat deployment) Ability : High-mobility close combat via ultra-high sync rate. ============================================================ Boarding Check (regular method — always calls base class) ============================================================ Boarding : Rei Ayanami — entry plug boarding complete. Boarding : Shinji Ikari — entry plug boarding complete. Boarding : Asuka Langley Soryu — entry plug boarding complete. ============================================================ Special Ability Activation (abstract virtual -> override) ============================================================ [Unit-00] Rei Ayanami — self-sacrifice AT Field deployment initiated. [Unit-01] Shinji Ikari — awakening mode activated, entering berserk state. [Unit-02] Asuka Langley Soryu — the best pilot fighting at full power! ============================================================ Sync Rate Update ============================================================ Rei Ayanami sync rate updated to 150 %. Shinji Ikari sync rate updated to 400 %. ============================================================ EVA Shutdown Sequence ============================================================ [Offline] EVA Unit-0 connection severed. [Offline] EVA Unit-1 connection severed. [Offline] EVA Unit-2 connection severed. ============================================================ Sequence Complete ============================================================
eva_static_dispatch.pas
A demonstration of static dispatch (no virtual). Declaring a method with the same name in a derived class does not override it — the base class method is called when using a base class type variable.
{$mode objfpc}
{
Compile and run:
fpc eva_static_dispatch.pas && ./eva_static_dispatch
}
program eva_static_dispatch;
type
TEva = class(TObject)
public
procedure Greet; { no virtual — static dispatch }
procedure Activate; virtual; { virtual — dynamic dispatch }
end;
type
TEvaUnit01 = class(TEva)
public
procedure Greet; { method hiding, not overriding }
procedure Activate; override;
end;
procedure TEva.Greet;
begin
WriteLn('TEva.Greet: General-purpose EVA.');
end;
procedure TEva.Activate;
begin
WriteLn('TEva.Activate: Preparing for activation.');
end;
procedure TEvaUnit01.Greet;
begin
WriteLn('TEvaUnit01.Greet: Unit-01 — Shinji Ikari is on board.');
end;
procedure TEvaUnit01.Activate;
begin
WriteLn('TEvaUnit01.Activate: Unit-01 — awakening mode activated.');
end;
var
obj : TEva; { declared type is TEva }
begin
obj := TEvaUnit01.Create;
try
obj.Greet; { static dispatch: TEva.Greet is called }
obj.Activate; { dynamic dispatch: TEvaUnit01.Activate is called }
finally
obj.Free;
end;
end.
fpc eva_static_dispatch.pas && ./eva_static_dispatch Free Pascal Compiler version ... Linking ./eva_static_dispatch TEva.Greet: General-purpose EVA. TEvaUnit01.Activate: Unit-01 — awakening mode activated.
eva_abstract.pas
An example of an abstract class. Instantiation is done through a concrete subclass.
{$mode objfpc}
{
Compile and run:
fpc eva_abstract.pas && ./eva_abstract
}
program eva_abstract;
type
{ Abstract base class — Activate must be implemented in derived classes }
TEva = class(TObject)
public
procedure Activate; virtual; abstract;
procedure ShowUnit; virtual;
end;
type
TEvaUnit00 = class(TEva)
public
procedure Activate; override;
procedure ShowUnit; override;
end;
type
TEvaUnit01 = class(TEva)
public
procedure Activate; override;
procedure ShowUnit; override;
end;
procedure TEva.ShowUnit;
begin
WriteLn('General EVA');
end;
procedure TEvaUnit00.Activate;
begin
WriteLn('Unit-00 — Rei Ayanami: AT Field deployment initiated.');
end;
procedure TEvaUnit00.ShowUnit;
begin
WriteLn('Unit-00 (Prototype) — Pilot: Rei Ayanami');
end;
procedure TEvaUnit01.Activate;
begin
WriteLn('Unit-01 — Shinji Ikari: Awakening mode activated.');
end;
procedure TEvaUnit01.ShowUnit;
begin
WriteLn('Unit-01 (Test Type) — Pilot: Shinji Ikari');
end;
var
evas : array[0..1] of TEva;
i : Integer;
begin
evas[0] := TEvaUnit00.Create;
evas[1] := TEvaUnit01.Create;
try
for i := 0 to 1 do begin
evas[i].ShowUnit;
evas[i].Activate;
end;
finally
for i := 0 to 1 do
evas[i].Free;
end;
end.
fpc eva_abstract.pas && ./eva_abstract Free Pascal Compiler version ... Linking ./eva_abstract Unit-00 (Prototype) — Pilot: Rei Ayanami Unit-00 — Rei Ayanami: AT Field deployment initiated. Unit-01 (Test Type) — Pilot: Shinji Ikari Unit-01 — Shinji Ikari: Awakening mode activated.
Overview
In Object Pascal, declaring a method with virtual in the base class and overriding it with override in the derived class enables dynamic dispatch — the method of the actual object type is called at runtime. Combining virtual; abstract; declares an abstract method with no implementation, forcing all derived classes to provide an override. Methods without virtual use static dispatch, always calling the method of the variable's declared type. Inside an overriding method, inherited can be used to reuse the base class implementation and avoid code duplication. For the basic class declaration and how to use constructors and destructors, see CLASS (class basics). For managing multiple files with units, see UNIT (unit basics).
If you find any errors or copyright issues, please contact us.