Language Atlas (mini-courses for more languages) Module 4 – Object Pascal with Free Pascal (Delphi-compatible)
Pascal classes, generics and exceptions
Write Object Pascal classes with properties, release every object with try and finally, raise your own exceptions, and use generics in objfpc and delphi mode.
What you will learn
- Define classes with properties, constructors and destructors, and free every object with try ... finally
- Find a leaked object with the heaptrc unit and fix the code that leaked it
- Handle bad input by raising exception classes of your own and catching them with on E: class do
- Use Generics.Collections in objfpc and delphi mode and write a generic container of your own
- Explain how COM interfaces count references, and why Free must not be mixed with them
Before you start
What you’ll learn · Before you start
What you will learn
- Define classes with properties, constructors and destructors, and free every object with try ... finally
- Find a leaked object with the heaptrc unit and fix the code that leaked it
- Handle bad input by raising exception classes of your own and catching them with on E: class do
- Use Generics.Collections in objfpc and delphi mode and write a generic container of your own
- Explain how COM interfaces count references, and why Free must not be mixed with them
Before you start
On this page
leak.pas creates one student object, prints it and ends, and Free Pascal’s heap report then says
1 unfreed memory blocks : 24: no garbage collector frees a class instance, so the 24 bytes stay allocated until the
program exits. In a service that handles a million records, the same line leaks at least 24 megabytes.
This lesson covers the habits that keep Object Pascal objects in order: who frees each object and how
try ... finally makes sure of it, properties that check a value on its way in, exception classes of your own and
handlers that choose by class, generic containers in both dialects, with a ring buffer you can step through, and
interfaces that count their references and free objects themselves.
A class instance lives on the heap until you free it
The Reference Guide puts it plainly: a class is a pointer to an object on the heap. Declaring s: TStudent reserves
room for the pointer only; TStudent.Create allocates the object and runs the constructor, and nothing frees it
until you call Free, which calls the destructor Destroy if the reference is not nil. For a Python or Java
programmer that is the big change, since there is no garbage collector for class instances. For a C programmer it is
malloc and free with a constructor and a destructor attached.
The three programs use one small unit, Student, whose destructor prints a line, so you can see each object go:
ownership/student.pas
unit Student;
{ A student with a name and a mark. The class is used by every program of this folder. }
{$mode objfpc}{$H+}
interface
type
TStudent = class
private
FName: string;
FMarks: Integer;
public
constructor Create(const AName: string; AMarks: Integer);
destructor Destroy; override;
property Name: string read FName;
property Marks: Integer read FMarks;
end;
implementation
constructor TStudent.Create(const AName: string; AMarks: Integer);
begin
inherited Create;
FName := AName;
FMarks := AMarks;
end;
destructor TStudent.Destroy;
begin
WriteLn(' freeing ', FName);
inherited Destroy;
end;
end. ownership/leak.pas
program Leak;
{ Creates a student and never frees it. Built with -gh, the heaptrc unit reports the block left over. }
{$mode objfpc}{$H+}
uses
Student;
var
s: TStudent;
begin
s := TStudent.Create('Asha', 91);
WriteLn(s.Name, ': ', s.Marks);
end. ownership/fixed.pas
program Fixed;
{ The same student, freed in a finally block, which runs however the block in try is left. }
{$mode objfpc}{$H+}
uses
Student;
var
s: TStudent;
begin
s := TStudent.Create('Asha', 91);
try
WriteLn(s.Name, ': ', s.Marks);
finally
s.Free;
end;
end. ownership/owned.pas
program Owned;
{ A list that owns its students: freeing the list frees every student in it. }
{$mode objfpc}{$H+}
uses
Generics.Collections, Student;
type
TStudentList = specialize TObjectList<TStudent>;
var
students: TStudentList;
s: TStudent;
begin
students := TStudentList.Create(True); { True: the list owns its items }
try
students.Add(TStudent.Create('Asha', 91));
students.Add(TStudent.Create('Ravi', 58));
students.Add(TStudent.Create('Meera', 74));
for s in students do
WriteLn(s.Name, ': ', s.Marks);
finally
students.Free;
end;
end. Each is built with -gh, which adds the heaptrc: A unit of Free Pascal's run-time library that counts every memory block a program allocates and frees, and prints a report when the program ends. Building with -gh adds it; a block still allocated at the end is a leak. unit; its documentation says it keeps
track of every allocation and, when the program exits, lists the blocks that were allocated but not freed:
from session import run
# Ownership: a student that is never freed, the heap report of the heaptrc unit (-gh), the same program with
# try ... finally, and a list that owns its students. -vn- hides the notes that Generics.Collections' own code
# produces when it is compiled.
run("""
cd ownership
fpc -l- -vi- -gh leak.pas
./leak
fpc -l- -vi- -gh fixed.pas
./fixed
fpc -l- -vi- -vn- -gh owned.pas
./owned
""") Output
$ cd ownership $ fpc -l- -vi- -gh leak.pas -macosx_version_min has been renamed to -macos_version_min ld: warning: -multiply_defined is obsolete $ ./leak Asha: 91 Heap dump by heaptrc unit of ./leak 1 memory blocks allocated : 24/24 0 memory blocks freed : 0/0 1 unfreed memory blocks : 24 True heap size : 327680 (32 used in System startup) True free heap : 327392 Should be : 327432 Call trace for block $… size 24 $… $… $ fpc -l- -vi- -gh fixed.pas -macosx_version_min has been renamed to -macos_version_min ld: warning: -multiply_defined is obsolete $ ./fixed Asha: 91 freeing Asha Heap dump by heaptrc unit of ./fixed 1 memory blocks allocated : 24/24 1 memory blocks freed : 24/24 0 unfreed memory blocks : 0 True heap size : 327680 (32 used in System startup) True free heap : 327648 $ fpc -l- -vi- -vn- -gh owned.pas -macosx_version_min has been renamed to -macos_version_min ld: warning: -multiply_defined is obsolete $ ./owned Asha: 91 Ravi: 58 Meera: 74 freeing Asha freeing Ravi freeing Meera Heap dump by heaptrc unit of ./owned 29 memory blocks allocated : 3400/3408 29 memory blocks freed : 3400/3408 0 unfreed memory blocks : 0 True heap size : 622592 (32 used in System startup) True free heap : 622560
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 ownership_session.py
leak.pas allocated one block of 24 bytes and freed none: 1 unfreed memory blocks : 24, followed by a call trace of
memory addresses, shown as $… because they change on every run. fixed.pas frees the student in a finally block,
and its report says 0 unfreed memory blocks : 0. owned.pas hands its three students to a list created with
TObjectList.Create(True), which owns them: freeing the list freed all three, and every one of the 29 blocks the
program allocated came back. Step through where Free runs in fixed.pas, on the normal path and when an exception
leaves the try block early:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Create first. s := TStudent.Create('Asha', 91) allocates the object on the heap, one block of 24 bytes in the heaptrc report. It comes before try: if the constructor fails, there is no object yet, and nothing to free.
- Use it inside try. The code between try and finally uses the object; here it prints Asha: 91.
- finally frees it. When the code inside try ends, the finally block runs. s.Free calls the destructor, which printed freeing Asha, and heaptrc then reported 0 unfreed memory blocks.
- On it goes. After end the program carries on with the next statement. leak.pas, which had no finally and no Free, ended with 1 unfreed block of 24 bytes instead.
- An exception inside try. If the code inside try raises an exception, the rest of it is skipped and control goes straight to finally, so the object is freed on this path too.
- The exception carries on. finally does not handle the exception. After it runs, the exception goes on to the nearest handler: in parse_marks.pas, finally printed done with "191" before the handler printed bad mark.
Create inside try
Writing try s := TStudent.Create(…); … finally s.Free; end; looks tidier but frees an unassigned variable when the
constructor itself raises an exception. The Reference Guide says a constructor that fails calls the destructor
automatically, so there is nothing left to free: write s := TStudent.Create(…) first, then try.
Checkpoint
Check: Who frees an object, and try ... finally
A property puts an API in front of a field
A property: A class member that reads and writes like a field but goes through the accessors it names, as in property Marks: Integer read FMarks write SetMarks. A property without a write accessor is read-only, and the compiler refuses an assignment to it. reads and writes like a field, but each access goes through what it
names. property Marks: Integer read FMarks write SetMarks reads the private field FMarks directly and sends every
assignment through the method SetMarks, which can refuse a bad value. The caller writes s.Marks := 91 either
way, so a field can become a checked property later without changing a single call site. property Name: string read FName, with no write, is read-only:
program Marked;
{ A property gives a field an API: reading Marks reads the field, writing it calls SetMarks, which checks the value.
The call sites look like plain field access. }
{$mode objfpc}{$H+}
uses
SysUtils;
type
EBadMarks = class(Exception);
TStudent = class
private
FName: string;
FMarks: Integer;
procedure SetMarks(Value: Integer);
public
constructor Create(const AName: string);
property Name: string read FName;
property Marks: Integer read FMarks write SetMarks;
end;
constructor TStudent.Create(const AName: string);
begin
inherited Create;
FName := AName;
end;
procedure TStudent.SetMarks(Value: Integer);
begin
if (Value < 0) or (Value > 100) then
raise EBadMarks.CreateFmt('%s: %d is not a mark from 0 to 100', [FName, Value]);
FMarks := Value;
end;
var
s: TStudent;
begin
s := TStudent.Create('Asha');
try
s.Marks := 91;
WriteLn(s.Name, ' has ', s.Marks);
try
s.Marks := 191;
except
on E: EBadMarks do WriteLn(E.ClassName, ': ', E.Message);
end;
WriteLn(s.Name, ' still has ', s.Marks);
finally
s.Free;
end;
end. from session import run
# A property that checks the value written to it, and a read-only property that the compiler protects.
run("""
cd props
fpc -l- -vi- marked.pas
./marked
!fpc -l- -vi- readonly.pas
""") Output
$ cd props $ fpc -l- -vi- marked.pas -macosx_version_min has been renamed to -macos_version_min ld: warning: -multiply_defined is obsolete $ ./marked Asha has 91 EBadMarks: Asha: 191 is not a mark from 0 to 100 Asha still has 91 $ fpc -l- -vi- readonly.pas readonly.pas(17,10) Error: No member is provided to access property readonly.pas(20) Fatal: There were 1 errors compiling module, stopping Fatal: Compilation aborted Error: …/bin/ppca64 returned an error exitcode [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 props_session.py
s.Marks := 191 became a call to SetMarks(191), which raised EBadMarks with the message
Asha: 191 is not a mark from 0 to 100, and the student still had 91 afterwards. The assignment to Name in
readonly.pas did not compile: No member is provided to access property.
| You write | What runs |
|---|---|
x := s.Marks |
reads the field FMarks |
s.Marks := 191 |
calls SetMarks(191), which may raise |
s.Name := 'Ravi' |
nothing: the compiler refuses it |
Python’s @property with a setter does the same job without changing how the call reads. In Java a getter and a
setter do it, and every caller then names the method.
Checkpoint
Check: Properties and their accessors
An exception travels to the first handler whose class matches
parse_marks.pas turns four pieces of text into marks. It declares a small hierarchy of its own,
EMarksError = class(Exception) with two children, EBadMarks and EMissingMarks, and it also meets
EConvertError, which SysUtils’ StrToInt raises for text that is not a number. Each input goes through a finally
block on its way to the handlers:
program ParseMarks;
{ Exceptions: a small hierarchy of your own, handlers that choose by class, a finally block that runs on the way out,
and at the end one exception that nobody handles. }
{$mode objfpc}{$H+}{$R+}
uses
SysUtils;
type
EMarksError = class(Exception); { every error of the marks report }
EBadMarks = class(EMarksError); { a mark outside 0..100 }
EMissingMarks = class(EMarksError); { no mark at all }
TMarks = 0..100;
const
Inputs: array[1..4] of string = ('91', '', '191', 'ninety');
function ToMarks(const Text: string): TMarks;
var
n: Integer;
begin
if Text = '' then
raise EMissingMarks.Create('no mark given');
n := StrToInt(Text); { raises EConvertError for 'ninety' }
if (n < 0) or (n > 100) then
raise EBadMarks.CreateFmt('%d is not a mark from 0 to 100', [n]);
Result := n;
end;
procedure Report(const Text: string);
var
m: TMarks;
begin
try
m := ToMarks(Text);
WriteLn('"', Text, '" -> ', m);
finally
WriteLn(' finally: done with "', Text, '"');
end;
end;
var
t: string;
begin
for t in Inputs do
try
Report(t);
except
on E: EBadMarks do WriteLn(' bad mark: ', E.Message);
on E: EMarksError do WriteLn(' ', E.ClassName, ': ', E.Message);
on E: EConvertError do WriteLn(' not a number: ', E.Message);
end;
WriteLn('now one that nobody handles:');
Report('101');
end. from session import run
# Four inputs through a parser that raises exceptions of its own and one of SysUtils, handlers that choose by class,
# finally blocks, and an exception that no handler catches.
run("""
cd errors
fpc -l- -vi- parse_marks.pas
!./parse_marks
""") Output
$ cd errors $ fpc -l- -vi- parse_marks.pas -macosx_version_min has been renamed to -macos_version_min ld: warning: -multiply_defined is obsolete $ ./parse_marks "91" -> 91 finally: done with "91" finally: done with "" EMissingMarks: no mark given finally: done with "191" bad mark: 191 is not a mark from 0 to 100 finally: done with "ninety" not a number: "ninety" is an invalid integer now one that nobody handles: finally: done with "101" An unhandled exception occurred at $…: EBadMarks: 101 is not a mark from 0 to 100 $… $… [exit status 217]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 errors_session.py
| Input | Raised by | Exception | Handler that ran |
|---|---|---|---|
'91' |
nothing | none | none: it printed 91 |
'' |
ToMarks |
EMissingMarks | on E: EMarksError |
'191' |
ToMarks |
EBadMarks | on E: EBadMarks |
'ninety' |
StrToInt |
EConvertError | on E: EConvertError |
The Reference Guide gives the rule: the handlers are tried in order, a handler matches an exception of its own class
or of a class derived from it, and the first match is used. So EBadMarks must come before its parent
EMarksError, or the parent’s handler would take it. Every finally: done line came before the handler’s line,
because a finally block runs as the exception leaves its try and then lets it carry on. The last call, with '101',
has no handler around it: the run printed An unhandled exception occurred, the class and the message, and stopped
with exit status 217, which the User’s Guide lists as an unhandled exception caught by SysUtils’ default handler.
One base class per program
Derive every exception of your own from one class, like EMarksError. A caller can then catch all of them with one
handler, while bugs such as EAccessViolation still reach the top and stop the program.
Checkpoint
Check: Raising and handling exceptions
Generics: one container for every type
Generics.Collections gives Object Pascal the containers Python and Java programmers expect: TList<T>,
TDictionary<TKey, TValue>, TObjectList<T> and more. A specialization: A type made from a generic by filling in its type parameters, such as TList<Integer> from TList<T>. objfpc mode writes it with the keyword specialize; delphi mode writes the type name alone. such as
TList<Integer> is the generic with its type filled in; in objfpc mode every specialization is written with
specialize, as the first lesson of this module showed. collections.pas sorts a list of marks and looks names up in
a dictionary:
program Collections;
{ Generics.Collections in objfpc mode: a list of marks and a dictionary from names to marks. }
{$mode objfpc}{$H+}
uses
Generics.Collections;
type
TMarkList = specialize TList<Integer>;
TMarkBook = specialize TDictionary<string, Integer>;
var
marks: TMarkList;
book: TMarkBook;
m, best: Integer;
begin
marks := TMarkList.Create;
book := TMarkBook.Create;
try
marks.AddRange([72, 58, 91, 66]);
marks.Sort;
Write('sorted:');
for m in marks do
Write(' ', m);
WriteLn;
book.Add('Asha', 91);
book.Add('Ravi', 58);
if book.TryGetValue('Asha', best) then
WriteLn('Asha: ', best);
WriteLn('Meera in the book: ', book.ContainsKey('Meera'), ', entries: ', book.Count);
finally
book.Free;
marks.Free;
end;
end. You can also write generics yourself. TRingBuffer<T> keeps up to a fixed number of items in a ring of slots: Push
writes at rear, Pop reads at front, both move on one slot and wrap from the last slot to slot 0, and two
exception classes report a full and an empty ring. The unit is written in delphi mode, and an objfpc program uses it
too, because each unit is compiled in its own mode:
generics/ringbuffer.pas
unit RingBuffer;
{ A generic ring buffer of fixed capacity, written in delphi mode. Push adds at rear and Pop takes from front; both
wrap round to slot 0 after the last slot, so no item ever moves. }
{$mode delphi}
interface
uses
SysUtils;
type
EBufferFull = class(Exception);
EBufferEmpty = class(Exception);
TRingBuffer<T> = class
private
FItems: array of T;
FFront, FRear, FCount: Integer;
function GetCapacity: Integer;
public
constructor Create(ACapacity: Integer);
procedure Push(const Item: T);
function Pop: T;
property Count: Integer read FCount;
property Capacity: Integer read GetCapacity;
property Front: Integer read FFront;
property Rear: Integer read FRear;
end;
implementation
constructor TRingBuffer<T>.Create(ACapacity: Integer);
begin
inherited Create;
SetLength(FItems, ACapacity);
end;
function TRingBuffer<T>.GetCapacity: Integer;
begin
Result := Length(FItems);
end;
procedure TRingBuffer<T>.Push(const Item: T);
begin
if FCount = Capacity then
raise EBufferFull.CreateFmt('ring buffer is full: %d of %d slots taken', [FCount, Capacity]);
FItems[FRear] := Item;
FRear := (FRear + 1) mod Capacity;
Inc(FCount);
end;
function TRingBuffer<T>.Pop: T;
begin
if FCount = 0 then
raise EBufferEmpty.Create('ring buffer is empty');
Result := FItems[FFront];
FFront := (FFront + 1) mod Capacity;
Dec(FCount);
end;
end. generics/ring_delphi.pas
program RingDelphi;
{ The ring buffer of marks that the figure steps through: 4 slots, nine operations, one refused. }
{$mode delphi}
uses
SysUtils, RingBuffer;
var
ring: TRingBuffer<Integer>;
procedure Show(const What: string);
begin
WriteLn(What, ': front ', ring.Front, ', rear ', ring.Rear, ', count ', ring.Count);
end;
procedure Push(Value: Integer);
begin
try
ring.Push(Value);
Show(Format('Push(%d)', [Value]));
except
on E: EBufferFull do WriteLn(Format('Push(%d): %s: %s', [Value, E.ClassName, E.Message]));
end;
end;
procedure Pop;
var
value: Integer;
begin
value := ring.Pop;
Show(Format('Pop = %d', [value]));
end;
begin
ring := TRingBuffer<Integer>.Create(4);
try
Push(72); Push(58); Push(91);
Pop;
Push(66); Push(40); Push(85);
Pop; Pop;
finally
ring.Free;
end;
end. generics/ring_objfpc.pas
program RingObjFpc;
{ The same unit, used from an objfpc program: here every use of the generic is written with specialize. Each unit
is compiled in its own mode. }
{$mode objfpc}{$H+}
uses
SysUtils, RingBuffer;
type
TNames = specialize TRingBuffer<string>;
var
names: TNames;
begin
names := TNames.Create(2);
try
names.Push('Asha');
names.Push('Ravi');
WriteLn('objfpc: popped ', names.Pop, ', then ', names.Pop);
try
names.Pop;
except
on E: EBufferEmpty do WriteLn('objfpc: ', E.ClassName, ': ', E.Message);
end;
finally
names.Free;
end;
end. from session import run
# Generics.Collections in objfpc mode, then a generic ring buffer written in delphi mode, used from a delphi program
# and from an objfpc one. -vn- hides the notes that Generics.Collections' own code produces when it is compiled.
run("""
cd generics
fpc -l- -vi- -vn- collections.pas
./collections
fpc -l- -vi- ring_delphi.pas
./ring_delphi
fpc -l- -vi- ring_objfpc.pas
./ring_objfpc
""") Output
$ cd generics $ fpc -l- -vi- -vn- collections.pas generics.dictionaries.inc(191,92) Warning: Constructing a class "TCustomDictionaryEnumerator$4$crc398437A0" with abstract method "DoMoveNext" generics.dictionaries.inc(191,92) Warning: Constructing a class "TCustomDictionaryEnumerator$4$crc398437A0" with abstract method "GetCurrent" generics.dictionaries.inc(191,92) Warning: Constructing a class "TCustomDictionaryEnumerator$4$crc74054DAE" with abstract method "DoMoveNext" generics.dictionaries.inc(191,92) Warning: Constructing a class "TCustomDictionaryEnumerator$4$crc74054DAE" with abstract method "GetCurrent" -macosx_version_min has been renamed to -macos_version_min ld: warning: -multiply_defined is obsolete $ ./collections sorted: 58 66 72 91 Asha: 91 Meera in the book: FALSE, entries: 2 $ fpc -l- -vi- ring_delphi.pas -macosx_version_min has been renamed to -macos_version_min ld: warning: -multiply_defined is obsolete $ ./ring_delphi Push(72): front 0, rear 1, count 1 Push(58): front 0, rear 2, count 2 Push(91): front 0, rear 3, count 3 Pop = 72: front 1, rear 3, count 2 Push(66): front 1, rear 0, count 3 Push(40): front 1, rear 1, count 4 Push(85): EBufferFull: ring buffer is full: 4 of 4 slots taken Pop = 58: front 2, rear 1, count 3 Pop = 91: front 3, rear 1, count 2 $ fpc -l- -vi- ring_objfpc.pas -macosx_version_min has been renamed to -macos_version_min ld: warning: -multiply_defined is obsolete $ ./ring_objfpc objfpc: popped Asha, then Ravi objfpc: EBufferEmpty: ring buffer is empty
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 generics_session.py
The four warnings of the first build come from generics.dictionaries.inc, the library’s own source, when
TDictionary is specialized in Free Pascal 3.2.2; your code is not their cause, and the program ran correctly.
-vn- hid the library’s notes. The ring buffer printed its front, rear and count after every operation. Step
through the same four slots and nine operations, with the slot each item lands in:
- being moved or written
Pseudo-code
-
enqueue(x): if count = capacity: error "full" -
a[rear] ← x; rear ← (rear + 1) mod capacity; count ← count + 1 -
dequeue(): if count = 0: error "empty" -
x ← a[front]; front ← (front + 1) mod capacity; count ← count − 1; return x
A circular queue with 4 slots. front is where the next item leaves, rear where the next one goes; both wrap around to slot 0 after slot 3.
When it finishes: 2 items queued.
Try your own input
Push(66) wrote slot 3 and wrapped rear round to 0; Push(40) filled the ring, so Push(85) raised EBufferFull
and changed nothing; the two last pops took 58 and 91 in the order they arrived. No item ever moved, only front and
rear. In objfpc mode the same unit served a ring of strings, specialize TRingBuffer<string>, and its empty Pop
raised EBufferEmpty.
Checkpoint
Check: Generic containers in both modes
Interfaces count their references and free the object themselves
An interface variable is different from a class variable. With the default COM-style interfaces, the Reference Guide
says every assignment to an interface variable updates a reference count, every variable that goes out of scope
decreases it, and when it reaches zero the object that implements the interface is usually freed. That is
reference counting: Freeing an object automatically when the last reference to it goes: each new reference adds one to a count and each released one subtracts one. Free Pascal counts COM-style interface references and AnsiStrings this way., the same mechanism that frees AnsiStrings, and
TInterfacedObject implements it for your classes:
unit Grader;
{ An interface and a class that implements it. TInterfacedObject counts the interface references to the object and
frees it when the count drops to zero. }
{$mode objfpc}{$H+}
interface
type
IGrader = interface
['{6F1C2A4E-8B3D-4C55-9E21-7A0B3C4D5E6F}']
function Grade(Marks: Integer): string;
end;
TPassGrader = class(TInterfacedObject, IGrader)
public
destructor Destroy; override;
function Grade(Marks: Integer): string;
end;
implementation
destructor TPassGrader.Destroy;
begin
WriteLn(' TPassGrader destroyed');
inherited Destroy;
end;
function TPassGrader.Grade(Marks: Integer): string;
begin
if Marks >= 40 then
Result := 'pass'
else
Result := 'fail';
end;
end. from session import run
# COM-style interfaces (the default) count references and free the object themselves; an object also freed by hand
# fails; CORBA-style interfaces count nothing, so the program frees the object itself.
run("""
cd interfaces
fpc -l- -vi- counted.pas
./counted
fpc -l- -vi- mixed.pas
!./mixed
fpc -l- -vi- corba.pas
./corba
""") Output
$ cd interfaces $ fpc -l- -vi- counted.pas -macosx_version_min has been renamed to -macos_version_min ld: warning: -multiply_defined is obsolete $ ./counted calling GradeOnce after Create: RefCount = 0 after g := obj: RefCount = 1 72 is a pass leaving GradeOnce TPassGrader destroyed back in the program $ fpc -l- -vi- mixed.pas -macosx_version_min has been renamed to -macos_version_min ld: warning: -multiply_defined is obsolete $ ./mixed 72 is a pass TPassGrader destroyed An unhandled exception occurred at $…: EInvalidPointer: Invalid pointer operation $… $… $… $… [exit status 217] $ fpc -l- -vi- corba.pas -macosx_version_min has been renamed to -macos_version_min ld: warning: -multiply_defined is obsolete $ ./corba corba: 35 is a fail corba: g := nil destroyed nothing TPassGrader destroyed
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 interfaces_session.py
counted.pas never calls Free, yet the object was destroyed as GradeOnce returned. mixed.pas calls obj.Free
while an interface variable still counts the object, and the program stopped with EInvalidPointer and exit
status 217. The Reference Guide warns about exactly this: freeing such an instance early is invalid, because the
compiler releases its interface references later and that release tries to free it again. Step through both runs:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- Create. obj := TPassGrader.Create makes the object. A plain object variable does not count references, so counted.pas printed RefCount = 0.
- An interface variable counts. g := obj stores a reference in an interface variable, and that adds one: RefCount = 1.
- Calls go through the interface. g.Grade(72) calls the object's method through the interface and returns pass. The count does not change.
- The count frees the object. When GradeOnce returns, g goes out of scope and its reference is released. The count falls to 0 and the object frees itself: TPassGrader destroyed printed before back in the program, with no Free anywhere.
- Now mixed.pas. mixed.pas sets up the same two references: obj, which does not count, and g, which does. The object is alive with RefCount 1.
- Free by hand. obj.Free destroys the object while g still counts it. The destructor printed TPassGrader destroyed, but obj.Free did not return normally.
- The program stops. The line after obj.Free never ran: the program stopped with EInvalidPointer: Invalid pointer operation and exit status 217. Choose one owner: the count, or Free, never both.
Choose one owner for each object. Reach it only through interfaces and let the count free it, or switch the counting
off: the Programmer’s Guide says {$interfaces corba} makes interfaces that have no parent and are not reference
counted, so the programmer does the bookkeeping. corba.pas set its interface variable to nil, which destroyed
nothing, and freed the object itself in a finally block.
Key idea
Every object has exactly one owner, a variable with try … finally, an owning list, or an interface count, and only that owner frees it.
Checkpoint
Check: Reference-counted interfaces
Practise: port the ring buffer to Python
The exercise ports TRingBuffer<T> to Python with its contract intact: the same slots and indexes, the same two
exception classes with the same messages, and read-only properties for count, capacity, front and rear, as
the Pascal class declares them. Its sample tests replay ring_delphi.pas and ring_objfpc.pas line for line.
Exercise · Medium · Python
Port the generic ring buffer and its exceptions to Python
The lesson's TRingBuffer<T> keeps items of any type in a fixed number of slots, and two exception classes say what went wrong. Port it to Python in ring.py, keeping its contract exactly, so that Python code and Pascal code built on it can be tested against the same cases.
BufferFullandBufferEmptyare exception classes derived fromException, asEBufferFullandEBufferEmptyderive from SysUtils'Exception.RingBuffer(capacity)starts empty, withfrontandrearat slot 0.push(item)puts the item in slotrearand movesrearone slot on, from the last slot back to 0. When every slot is taken it raisesBufferFullwith the messagering buffer is full: 4 of 4 slots taken(with the buffer's own numbers), and it changes nothing.pop()returns the item in slotfrontand movesfronton in the same way. When the buffer is empty it raisesBufferEmpty("ring buffer is empty").count,capacity,frontandrearare read-only properties, as in the Pascal class: reading them works, assigning to them raisesAttributeError.
The sample tests replay ring_delphi.pas from the lesson, nine operations on four slots with one push refused, and ring_objfpc.pas, two names and an empty pop.
Starter code · ring.py
class BufferFull(Exception):
"""Raised by push when every slot is taken."""
class BufferEmpty(Exception):
"""Raised by pop when there is nothing to take."""
class RingBuffer:
"""A ring buffer of fixed capacity, like the lesson's TRingBuffer<T>."""
def __init__(self, capacity):
# Replace this class body with your code.
self.items = []
self.capacity = capacity
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop() The sample tests · test_ring.py
from ring import BufferEmpty, BufferFull, RingBuffer
def state(ring):
return (ring.front, ring.rear, ring.count)
def raised(action):
"""The exception that action() raises, or None."""
try:
action()
except Exception as error: # noqa: BLE001 - the tests look at the class and the message
return error
return None
def test_three_pushes_and_a_pop():
"""ring_delphi.pas: Push(72), Push(58), Push(91), then Pop = 72"""
ring = RingBuffer(4)
ring.push(72)
assert state(ring) == (0, 1, 1)
ring.push(58)
ring.push(91)
assert state(ring) == (0, 3, 3)
assert ring.pop() == 72
assert state(ring) == (1, 3, 2)
def test_rear_wraps_round():
"""Push(66) puts rear back at slot 0; Push(40) fills the ring"""
ring = RingBuffer(4)
for value in (72, 58, 91):
ring.push(value)
ring.pop()
ring.push(66)
assert state(ring) == (1, 0, 3)
ring.push(40)
assert state(ring) == (1, 1, 4)
def test_full_ring_refuses_and_keeps_its_state():
"""Push(85) on a full ring raises BufferFull with the Pascal message and changes nothing"""
ring = RingBuffer(4)
for value in (72, 58, 91):
ring.push(value)
ring.pop()
ring.push(66)
ring.push(40)
error = raised(lambda: ring.push(85))
assert isinstance(error, BufferFull)
assert str(error) == "ring buffer is full: 4 of 4 slots taken"
assert state(ring) == (1, 1, 4)
def test_pops_come_out_in_order():
"""after the refused push, Pop gives 58 then 91, as the recorded run printed"""
ring = RingBuffer(4)
for value in (72, 58, 91):
ring.push(value)
ring.pop()
ring.push(66)
ring.push(40)
raised(lambda: ring.push(85))
assert ring.pop() == 58
assert ring.pop() == 91
assert state(ring) == (3, 1, 2)
def test_any_item_type_and_empty_pop():
"""ring_objfpc.pas: two names, then a pop from the empty ring raises BufferEmpty"""
names = RingBuffer(2)
names.push("Asha")
names.push("Ravi")
assert (names.pop(), names.pop()) == ("Asha", "Ravi")
error = raised(names.pop)
assert isinstance(error, BufferEmpty)
assert str(error) == "ring buffer is empty"
assert issubclass(BufferEmpty, Exception) and issubclass(BufferFull, Exception)
assert not issubclass(BufferEmpty, BufferFull) and not issubclass(BufferFull, BufferEmpty)
def test_read_only_properties():
"""count, capacity, front and rear can be read but not assigned"""
ring = RingBuffer(3)
assert ring.capacity == 3 and ring.count == 0
for name in ("count", "capacity", "front", "rear"):
assert isinstance(raised(lambda: setattr(ring, name, 1)), AttributeError) A hint
Keep a list of capacity slots and three numbers, front, rear and count, exactly as the Pascal class does; (rear + 1) % capacity moves an index on and wraps it. Python's @property without a setter gives you a read-only property, like property Count: Integer read FCount.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Then take the self-check on the lesson’s five ideas, from ownership to interfaces. It opens with the idea your answers so far leave weakest, and each pick is explained on the spot:
Practice that adapts to you
Practise this lesson
15 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to mid-level): Why does Object Pascal code write Create, then try … finally Free end, and why
does Create come before try?
A class instance lives on the heap and nothing frees it automatically, so whoever creates an object must free it on
every way out of the code that uses it, including an exception. try … finally guarantees that: the finally block
runs whether the try block ends normally, exits early or raises, and then lets any exception carry on to a handler.
Create goes before try because the constructor may fail. If it raises, the Reference Guide says the destructor runs
automatically, and the variable was never assigned; a Free in the finally block would then work on garbage. With
Create first, try is only entered once there is an object to free. In the lesson, leak.pas without this pattern
left one 24-byte block, and fixed.pas with it left none.
Two questions of the kind a team asks someone who will own Object Pascal code:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to senior
A long-running Free Pascal service grows by about 24 bytes for every student record it processes, and after a busy week it is restarted to get its memory back. How do you find the leak, fix it, and keep it from coming back?
-
Mid to senior
A procedure stops with EInvalidPointer: Invalid pointer operation, sometimes at a Free call and sometimes when it returns. The object involved implements an interface. What is the likely cause, and how do you fix the design rather than the symptom?
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
Who frees an Object Pascal class instance?
Your own code: a class variable points to an object on the heap, and nothing frees it until Free runs its destructor. leak.pas, which never did, ended with one 24-byte block unfreed.
-
Why is s := TStudent.Create(…) written before try, not inside it?
If the constructor fails, the destructor runs by itself and s is never assigned, so a Free in finally would act on garbage. Created first, the object exists whenever try is entered.
-
What does the build option -gh add to a Free Pascal program?
The heaptrc unit: it records every allocation and, at exit, reports the blocks that were never freed, such as 1 unfreed memory blocks : 24.
-
What does a list created with TObjectList.Create(True) do with the objects put in it?
It owns them: freeing the list frees each of them too, so the program frees only the list.
-
Given property Marks: Integer read FMarks write SetMarks, what runs for s.Marks := 191?
A call to SetMarks(191), which can refuse the value by raising. Reading s.Marks reads FMarks directly, and a property with no write cannot be assigned at all.
-
In which order do except handlers for EBadMarks and its parent EMarksError go?
The child first. Handlers are tried top to bottom and the first one whose class matches wins, so a parent placed first would catch its children as well.
-
What happens when a Free Pascal exception reaches no handler at all?
SysUtils' default handler reports that an unhandled exception occurred, with the class and the message, and ends the program with exit status 217.
-
Why must you never call Free on an object still held by a COM-style interface variable?
The interface references are counted, and releasing the last one frees the object a second time; in the lesson that stopped with EInvalidPointer. Give each object one owner: the count, or {$interfaces corba} and your own Free.
Key takeaways
- A class instance lives on the heap until it is freed;
Create, thentry … finally Free endfrees it on every path, and building with-ghmakes heaptrc list any block that was never freed. - A list created with
TObjectList.Create(True)owns its items and frees them with itself. - A property reads and writes like a field but goes through its accessors, so a setter can refuse a bad value and a
property without
writecannot be assigned at all. - Handlers are tried in order and the first whose class matches wins, so specific classes come before their parents;
finallyblocks run first, and an exception nobody handles ends the program with exit status 217. - Generics.Collections and your own generics work in both modes, with
specializein objfpc and without it in delphi mode, and each unit is compiled in the mode it declares. - COM interfaces count references and free their object when the count reaches zero, so never also call
Free;{$interfaces corba}switches the counting off.
Terms in this lesson
- heaptrc
- A unit of Free Pascal's run-time library that counts every memory block a program allocates and frees, and prints a report when the program ends. Building with -gh adds it; a block still allocated at the end is a leak.
- property
- A class member that reads and writes like a field but goes through the accessors it names, as in property Marks: Integer read FMarks write SetMarks. A property without a write accessor is read-only, and the compiler refuses an assignment to it.
- reference counting
- Freeing an object automatically when the last reference to it goes: each new reference adds one to a count and each released one subtracts one. Free Pascal counts COM-style interface references and AnsiStrings this way.
- specialization
- A type made from a generic by filling in its type parameters, such as TList<Integer> from TList<T>. objfpc mode writes it with the keyword specialize; delphi mode writes the type name alone.
References
- Reference Guide 6.4: Class instantiation (Free Pascal team)
- Reference Guide 6.5: Class destruction (Free Pascal team)
- Reference Guide 6.7: Properties (Free Pascal team)
- RTL reference: the heaptrc unit (Free Pascal team)
- RTL reference: using the heaptrc unit (Free Pascal team)
- Reference Guide 17.2: The try...except statement (Free Pascal team)
- Reference Guide 17.3: The try...finally statement (Free Pascal team)
- User's Guide, appendix D: Run-time errors (Free Pascal team)
- Reference Guide 8.1: Generics, introduction (Free Pascal team)
- Reference Guide 8.3: Generic type specialization (Free Pascal team)
- Reference Guide 8.5.1: Generics in Delphi mode, syntax elements (Free Pascal team)
- Reference Guide 7.8: Reference counting (Free Pascal team)
- Programmer's Guide 1.2.37: $INTERFACES (Free Pascal team)
Report a problem with this lesson
Kept only in this browser. Your Learn progress