Sign in Join
Wiki copytemporary

management operators: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Management operators feature
22
3-From Free Pascal version 3.1.1 onwards, there is a new language feature called management operators for extended or advanced records.
3+From Free Pascal version 3.1.1 onwards, there is a new language feature called management operators for extended or advanced records.
44
5-The new operators are: Initialize, Finalize, AddRef and Copy.
5+The new operators are: Initialize, Finalize, AddRef and Copy.
66
7-These are a fairly unique feature, and are called "management operators" because:
7+These are a fairly unique feature, and are called "management operators" because:
88
9-- Each record, even non-managed or empty, that implements any management operators becomes a managed type.
10-- They make it possible to implement new custom types with their own memory management, e.g: new string types, fast TValue implementations without hacks on the RTL, etc.
9+ * Each record, even non-managed or empty, that implements any management operators becomes a managed type.
10+ * They make it possible to implement new custom types with their own memory management, e.g: new string types, fast TValue implementations without hacks on the RTL, etc.
1111
12-Management operators have no result type as opposed to normal operators, and work via a very simple VMT.
12+Management operators have no result type as opposed to normal operators, and work via a very simple VMT.
1313
14-Thanks to this, it is possible to combine management operators with all low-level RTL functions, such as InitializeArray / FinalizeArray / etc.
14+Thanks to this, it is possible to combine management operators with all low-level RTL functions, such as InitializeArray / FinalizeArray / etc.
1515
16-Management operators can be used for many things:
16+Management operators can be used for many things:
1717
18-- More granularly controlling the lifetimes of simple value types / primitives
19-- Implementing "nullable" value types
20-- Custom ARC implementations
21-- A very fast RTTI.TValue implementation
22-- As a replacement for manually-called Init/Done record methods like the popular "mORMot" library uses for many types (for example in SynCommons.TSynLocker).
23-- Auto init/finit for pointers/classes/simple types or anything else we have in Pascal.
24-- Much more
18+ * More granularly controlling the lifetimes of simple value types / primitives
19+ * Implementing "nullable" value types
20+ * Custom ARC implementations
21+ * A very fast RTTI.TValue implementation
22+ * As a replacement for manually-called Init/Done record methods like the popular "mORMot" library uses for many types (for example in SynCommons.TSynLocker).
23+ * Auto init/finit for pointers/classes/simple types or anything else we have in Pascal.
24+ * Much more
2525
26-They work correctly in all possible ways with the RTL:
26+They work correctly in all possible ways with the RTL:
2727
28-- New (Initialize).
29-- Dispose (Finalize).
30-- Initialize (Initialize).
31-- Finalize (Finalize).
32-- InitializeArray (Initialize).
33-- FinalizeArray (Finalize).
34-- SetLength (Initialize/Finalize).
35-- Copy (AddRef).
36-- RTTI.IsManaged.
28+ * New (Initialize).
29+ * Dispose (Finalize).
30+ * Initialize (Initialize).
31+ * Finalize (Finalize).
32+ * InitializeArray (Initialize).
33+ * FinalizeArray (Finalize).
34+ * SetLength (Initialize/Finalize).
35+ * Copy (AddRef).
36+ * RTTI.IsManaged.
3737
38-Management operators, when implemented, are called implicitly at various times. For example:
38+Management operators, when implemented, are called implicitly at various times. For example:
3939
40-- Global variables (Initialize/Finalize).
41-- Local variables (Initialize/Finalize).
42-- For fields inside records, objects or classes (Initialize/Finalize).
43-- Variable assignment (Copy).
44-- For parameters for routines - AddRef/Finalize/none - this depends on modifiers like var / constref / const.
40+ * Global variables (Initialize/Finalize).
41+ * Local variables (Initialize/Finalize).
42+ * For fields inside records, objects or classes (Initialize/Finalize).
43+ * Variable assignment (Copy).
44+ * For parameters for routines - AddRef/Finalize/none - this depends on modifiers like var / constref / const.
4545
4646## Initialize
4747
48-The initialize operator is called directly after stack (or heap) memory allocation for a record happens.
49-
50-It allows for custom automatic initialization code.
48+The initialize operator is called directly after stack (or heap) memory allocation for a record happens.
5149
52-``` pascal
50+It allows for custom automatic initialization code.
51+```
5352program TestInitialize;
54-
55-{$if FPC_FULLVERSION < 30101}
56-{$ERROR this demo needs version 3.1.1}
57-{$endif}
58-{$mode delphi}
59-
60-type
61- PRec = ^TRec;
62-
63- TRec = record
64- I: Integer;
65- class operator Initialize(var aRec: TRec);
66- end;
67-
68- class operator TRec.Initialize(var aRec: TRec);
69- begin
70- aRec := Default(TRec); // initialize to default
71- end;
72-
73- procedure PrintTRec(r: PRec);
74- begin
75- WriteLn('Initialized TRec field i: ', r^.I); // should always be zero, stack or heap
76- end;
53+
54+ {$if FPC_FULLVERSION < 30101}
55+ {$ERROR this demo needs version 3.1.1}
56+ {$endif}
57+ {$mode delphi}
58+
59+ type
60+ PRec = ^TRec;
61+
62+ TRec = record
63+ I: Integer;
64+ class operator Initialize(var aRec: TRec);
65+ end;
66+
67+ class operator TRec.Initialize(var aRec: TRec);
68+ begin
69+ aRec.I := 0;
70+ end;
71+
72+ procedure PrintTRec(r: PRec);
73+ begin
74+ WriteLn('Initialized TRec field i: ', r^.I); // should always be zero, stack or heap
75+ end;
76+
77+ var
78+ a, b: PRec;
79+
80+ begin
81+ New(a);
82+ New(b); // standard "new" does not initialize, but now it does!
83+ PrintTRec(a);
84+ PrintTRec(b);
85+ Dispose(a);
86+ Dispose(b);
87+ end.
88+```
7789
78-var
79- a, b: PRec;
90+
8091
81-begin
82- New(a);
83- New(b); // standard "new" does not initialize, but now it does!
84- PrintTRec(a);
85- PrintTRec(b);
86- Dispose(a);
87- Dispose(b);
88-end.
92+Warning: Do not use the Default() intrinsic inside the initialize operator because that causes initialize to be called twice. Use other methods to initialize a record, such as fillchar.
93+An example on how to initialize properly without calling Default() or system.initialize is:
8994```
95+{$mode objfpc}{$modeswitch advancedrecords}
96+ type
97+ array10 = array[0..9] of byte;
98+
99+ TTest = record
100+ fields:array10;
101+ class operator initialize(var value:TTest);
102+ end;
103+
104+ class operator ttest.initialize(var value:TTest);
105+ begin
106+ // sizeof takes only the fields of the record in to account
107+ fillbyte(value,sizeof(TTest),0);// do not use default(), that triggers initialize twice
108+ end;
109+
110+ procedure testme;
111+ var
112+ a,b:TTest;// stack
113+ i:integer;
114+ begin
115+ a.fields:=[1,2,3,4,5,6,7,8,9,10];
116+ a:=b;
117+ for i :=0 to 9 do
118+ write(a.fields[i]:3);
119+ end;
120+
121+ begin
122+ testme;
123+ end.
124+```
90125
91126## Finalize
92127
93-Finalize is called when a record goes out of scope.
94-
95-It is useful for automatic custom finalization code.
128+Finalize is called when a record goes out of scope.
96129
97-``` pascal
130+It is useful for automatic custom finalization code.
131+```
98132program TestFinalize;
99-
100-{$if FPC_FULLVERSION < 30101}
101-{$ERROR this demo needs version 3.1.1}
102-{$endif}
103-{$mode delphi}
104-
105-type
106- PRec = ^TRec;
107-
108- TRec = record
109- I: Integer;
110- class operator Finalize(var aRec: TRec);
111- end;
112-
113- class operator TRec.Finalize(var aRec: TRec);
114- begin
115- WriteLn('Just to let you know: I am finalizing..');
116- end;
117-
118-var
119- a, b: PRec;
120- c: array of TRec;
121-
122-begin
123- New(a);
124- New(b);
125- Dispose(a);
126- Dispose(b);
127- WriteLn('Just before program termination this will also be finalized');
128- SetLength(c, 4);
129-end.
133+
134+ {$if FPC_FULLVERSION < 30101}
135+ {$ERROR this demo needs version 3.1.1}
136+ {$endif}
137+ {$mode delphi}
138+
139+ type
140+ PRec = ^TRec;
141+
142+ TRec = record
143+ I: Integer;
144+ class operator Finalize(var aRec: TRec);
145+ end;
146+
147+ class operator TRec.Finalize(var aRec: TRec);
148+ begin
149+ WriteLn('Just to let you know: I am finalizing..');
150+ end;
151+
152+ var
153+ a, b: PRec;
154+ c: array of TRec;
155+
156+ begin
157+ New(a);
158+ New(b);
159+ Dispose(a);
160+ Dispose(b);
161+ WriteLn('Just before program termination this will also be finalized');
162+ SetLength(c, 4);
163+ end.
130164```
131165
132166## AddRef
133167
134-AddRef is called after the contents of a record have been duplicated by a bitwise copy (for example **after**, not during, an assigment.)
135-
136-By itself it does not do any lifetime management, but you can use it to implement it. See also Copy.
168+AddRef is called after the contents of a record have been duplicated by a bitwise copy (for example **after** , not during, an assigment.)
137169
138-``` pascal
170+By itself it does not do any lifetime management, but you can use it to implement it. See also Copy.
171+```
139172program TestAddref;
140-
141-{$if FPC_FULLVERSION < 30101}
142-{$ERROR this demo needs version 3.1.1}
143-{$endif}
144-{$mode delphi}
145-
146-uses
147- SysUtils;
148-
149-type
150- PRec = ^TRec;
151-
152- TRec = record
153- I: Integer;
154- class operator AddRef(var aRec: TRec);
155- end;
156-
157- class operator TRec.AddRef(var aRec: TRec);
158- begin
159- WriteLn('Just to let you know: maybe you can do lifetime management here..');
160- end;
161-
162-var
163- a, b: array of TRec;
164-
165-begin
166- SetLength(a, 4);
167- b := Copy(a);
168-end.
173+
174+ {$if FPC_FULLVERSION < 30101}
175+ {$ERROR this demo needs version 3.1.1}
176+ {$endif}
177+ {$mode delphi}
178+
179+ uses
180+ SysUtils;
181+
182+ type
183+ PRec = ^TRec;
184+
185+ TRec = record
186+ I: Integer;
187+ class operator AddRef(var aRec: TRec);
188+ end;
189+
190+ class operator TRec.AddRef(var aRec: TRec);
191+ begin
192+ WriteLn('Just to let you know: maybe you can do lifetime management here..');
193+ end;
194+
195+ var
196+ a, b: array of TRec;
197+
198+ begin
199+ SetLength(a, 4);
200+ b := Copy(a);
201+ end.
169202```
170203
171204## Copy
172205
173-The Copy operator, if implemented, is called instead of the default copy behavior. This operator is responsible for copying everything that's needed from the source to the target.
174-
175-This is very helpfull if your record contains e.g. a dynamic array.\
176-
177-Consider the following program:
206+The Copy operator, if implemented, is called instead of the default copy behavior. This operator is responsible for copying everything that's needed from the source to the target.
178207
179-``` pascal
208+This is very helpfull if your record contains e.g. a dynamic array.
209+
210+Consider the following program:
211+```
180212uses
181- SysUtils;
182-
183-type
184- TArr = array of integer;
185- TRec = record
186- Arr: TArr;
187- end;
188-
189-function ArrToString(Arr: TArr): String;
190-var
191- X: Integer;
192-begin
193- Result := '';
194- for X in Arr do Result := Result + IntToStr(X) + ',';
195- if Result <> '' then Delete(Result, Length(Result), 1);
196- Result := '[' + Result + ']';
197-end;
198-
199-var
200- R1, R2: TRec;
201-
202-begin
203- writeln('Normal record type with a dynamic array');
204- R1.Arr := [1,2,3];
205- R2 := R1;
206- writeln('R1.Arr = ',ArrToString(R1.Arr)); //[1,2,3]
207- writeln('R2.Arr = ',ArrToString(R2.Arr)); //[1,2,3]
208- writeln('After R2.Arr[0] := 666');
209- R2.Arr[0] := 666;
210- writeln('R1.Arr = ',ArrToString(R1.Arr)); //[666,2,3]
211- writeln('R2.Arr = ',ArrToString(R2.Arr)); //[666,2,3]
212-end.
213+ SysUtils;
214+
215+ type
216+ TArr = array of integer;
217+ TRec = record
218+ Arr: TArr;
219+ end;
220+
221+ implementation
222+
223+ function ArrToString(Arr: TArr): String;
224+ var
225+ X: Integer;
226+ begin
227+ Result := '';
228+ for X in Arr do Result := Result + IntToStr(X) + ',';
229+ if Result <> '' then Delete(Result, Length(Result), 1);
230+ Result := '[' + Result + ']';
231+ end;
232+
233+ var
234+ R1, R2: TRec;
235+
236+ begin
237+ writeln('Normal record type with a dynamic array');
238+ R1.Arr := [1,2,3];
239+ R2 := R1;
240+ writeln('R1.Arr = ',ArrToString(R1.Arr)); //[1,2,3]
241+ writeln('R2.Arr = ',ArrToString(R2.Arr)); //[1,2,3]
242+ writeln('After R2.Arr[0] := 666');
243+ R2.Arr[0] := 666;
244+ writeln('R1.Arr = ',ArrToString(R1.Arr)); //[666,2,3]
245+ writeln('R2.Arr = ',ArrToString(R2.Arr)); //[666,2,3]
246+ end.
213247```
214248
215-This will output:
216-
217- Normal record type with a dynamic array
249+This will output:
250+```
251+Normal record type with a dynamic array
218252 R1.Arr = [1,2,3]
219253 R2.Arr = [1,2,3]
220- After R2.Arr[0] := 666
254+ After R2.Arr[0] := 666
221255 R1.Arr = [666,2,3]
222256 R2.Arr = [666,2,3]
223-
224-Notice that changing `R2.Arr` changes `R1.Arr` as well, since both the arrays point to the same memory location.\
225-When you do `R2 := R1`, you do not copy the contents of R1.Arr into R2, you copy the pointer that points to the first element of R1.Arr.\
226-If you want to avoid this, you could write a workaround like:
257+```
227258
228-``` pascal
259+Notice that changing `R2.Arr` changes `R1.Arr` as well, since both the arrays point to the same memory location.
260+When you do `R2 := R1`, you do not copy the contents of R1.Arr into R2, you copy the pointer that points to the first element of R1.Arr.
261+If you want to avoid this, you could write a workaround like:
262+```
229263function CopyFrom(Src: TRec): TRec;
230-begin
231- Result.Arr := Copy(Src.Arr);
232-end;
264+ begin
265+ Result.Arr := Copy(Src.Arr);
266+ end;
233267```
234268
235-And then always use `R2 := CopyFrom(R1)` instead of `R2 := R1`.\
236-
237-Or you can solve this problem using the Copy management operator:
238-
239-``` pascal
269+And then always use `R2 := CopyFrom(R1)` instead of `R2 := R1`.
270+
271+Or you can solve this problem using the Copy management operator:
272+```
240273{$modeswitch advancedrecords}
241-uses
242- SysUtils;
243-
244-type
245- TManagedRec = record
246- Arr: TArr;
247- class operator Copy(constref aSrc: TManagedRec; var aDst: TManagedRec);
248- end;
249-
250-{ TMangedRec }
251-class operator TManagedRec.Copy(constref aSrc: TManagedRec; var aDst: TManagedRec);
252-begin
253- aDst.Arr := Copy(aSrc.Arr);
254-end;
255-
256-function ArrToString(Arr: TArr): String;
257-var
258- X: Integer;
259-begin
260- Result := '';
261- for X in Arr do Result := Result + IntToStr(X) + ',';
262- if Result <> '' then Delete(Result, Length(Result), 1);
263- Result := '[' + Result + ']';
264-end;
265-
266-var
267- M1, M2: TManagedRec;
268-begin
269- writeln('Managed record type with a dynamic array and class operator Copy');
270- M1.Arr := [1,2,3];
271- M2 := M1;
272- writeln('M1.Arr = ',ArrToString(M1.Arr)); //[1,2,3]
273- writeln('M2.Arr = ',ArrToString(M2.Arr)); //[1,2,3]
274- writeln('After M2.Arr[0] := 666');
275- M2.Arr[0] := 666;
276- writeln('M1.Arr = ',ArrToString(M1.Arr)); //[1,2,3]
277- writeln('M2.Arr = ',ArrToString(M2.Arr)); //[666,2,3]
278-end.
274+ uses
275+ SysUtils;
276+
277+ type
278+ TArr = array of integer;
279+ TManagedRec = record
280+ Arr: TArr;
281+ class operator Copy(constref aSrc: TManagedRec; var aDst: TManagedRec);
282+ end;
283+ implementation
284+ { TMangedRec }
285+ class operator TManagedRec.Copy(constref aSrc: TManagedRec; var aDst: TManagedRec);
286+ begin
287+ aDst.Arr := Copy(aSrc.Arr);
288+ end;
289+
290+ function ArrToString(Arr: TArr): String;
291+ var
292+ X: Integer;
293+ begin
294+ Result := '';
295+ for X in Arr do Result := Result + IntToStr(X) + ',';
296+ if Result <> '' then Delete(Result, Length(Result), 1);
297+ Result := '[' + Result + ']';
298+ end;
299+
300+ var
301+ M1, M2: TManagedRec;
302+ begin
303+ writeln('Managed record type with a dynamic array and class operator Copy');
304+ M1.Arr := [1,2,3];
305+ M2 := M1;
306+ writeln('M1.Arr = ',ArrToString(M1.Arr)); //[1,2,3]
307+ writeln('M2.Arr = ',ArrToString(M2.Arr)); //[1,2,3]
308+ writeln('After M2.Arr[0] := 666');
309+ M2.Arr[0] := 666;
310+ writeln('M1.Arr = ',ArrToString(M1.Arr)); //[1,2,3]
311+ writeln('M2.Arr = ',ArrToString(M2.Arr)); //[666,2,3]
312+ end.
279313```
280314
281-This will output:
282-
283- Managed record type with a dynamic array and class operator Copy
315+This will output:
316+```
317+Managed record type with a dynamic array and class operator Copy
284318 M1.Arr = [1,2,3]
285319 M2.Arr = [1,2,3]
286- After M2.Arr[0] := 666
320+ After M2.Arr[0] := 666
287321 M1.Arr = [1,2,3]
288322 M2.Arr = [666,2,3]
323+```
289324
290-There is also a (more complex) example in [1](https://gitlab.com/freepascal.org/fpc/source/-/blob/main/tests/test/tmoperator8.pp) within the FPC sources.
325+There is also a (more complex) example in [[1]](<https://gitlab.com/freepascal.org/fpc/source/-/blob/main/tests/test/tmoperator8.pp>) within the FPC sources.
291326
292327## Example of using Initialize and Finalize
293-
294-``` pascal
328+```
295329unit UResourceHandlers;
296-
297-{$if FPC_FULLVERSION < 30101}
298-{$ERROR this demo needs version 3.1.1}
299-{$endif}
300-{$mode delphi}
301-
302-interface
303-
304-uses
305- Classes, SysUtils;
306-
307-type
308-
309- { TObjectHandler }
310-
311- TObjectHandler = record
312- obj: TObject;
313- class operator Initialize(var hdl: TObjectHandler);
314- class operator Finalize(var hdl: TObjectHandler);
315- end;
316-
317-implementation
318-
319-{ TObjectHandler }
320-
321-class operator TObjectHandler.Initialize(var hdl: TObjectHandler);
322-begin
323- hdl.obj := nil;
324-end;
325-
326-class operator TObjectHandler.finalize(var hdl: TObjectHandler);
327-begin
328- FreeAndNil(hdl.obj);
329-end;
330-
331-end.
330+
331+ {$if FPC_FULLVERSION < 30101}
332+ {$ERROR this demo needs version 3.1.1}
333+ {$endif}
334+ {$mode delphi}
335+
336+ interface
337+
338+ uses
339+ Classes, SysUtils;
340+
341+ type
342+
343+ { TObjectHandler }
344+
345+ TObjectHandler = record
346+ obj: TObject;
347+ class operator Initialize(var hdl: TObjectHandler);
348+ class operator Finalize(var hdl: TObjectHandler);
349+ end;
350+
351+ implementation
352+
353+ { TObjectHandler }
354+
355+ class operator TObjectHandler.Initialize(var hdl: TObjectHandler);
356+ begin
357+ hdl.obj := nil;
358+ end;
359+
360+ class operator TObjectHandler.finalize(var hdl: TObjectHandler);
361+ begin
362+ FreeAndNil(hdl.obj);
363+ end;
364+
365+ end.
332366```
333367
334-How to use it
335-
336-``` pascal
368+How to use it
369+```
337370procedure ExtractionResultTests.ObjectHandlerTest;
338-var
339- a: TRow;
340- ah: TObjectHandler;
341-begin
342- a := TRow.Create;
343- ah.obj := a;
344-end;
371+ var
372+ a: TRow;
373+ ah: TObjectHandler;
374+ begin
375+ a := TRow.Create;
376+ ah.obj := a;
377+ end;
345378```
346379
347380In this case the destructor of the TRow object is called when the handler goes out of scope. The same idea could be used for other resources like TMutex / TCriticalSection / anything else along those lines.
348381
349382---
350-*From the Free Pascal wiki, page "management operators", as it stood on 2024-04-26 after a change by Bart, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
351-Category: FPC
383+*From the Free Pascal wiki, page "management operators", as the Internet Archive kept it on 2025-12-12, last changed on 2025-03-02. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history