Sign in Join
Wiki copytemporary

management operators: what changed

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

Title: management operators

OldNewLine
1+## Management operators feature
2+
3+From Free Pascal version 3.1.1 onwards, there is a new language feature called management operators for extended or advanced records.
4+
5+The new operators are: Initialize, Finalize, AddRef and Copy.
6+
7+These are a fairly unique feature, and are called "management operators" because:
8+
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.
11+
12+Management operators have no result type as opposed to normal operators, and work via a very simple VMT.
13+
14+Thanks to this, it is possible to combine management operators with all low-level RTL functions, such as InitializeArray / FinalizeArray / etc.
15+
16+Management operators can be used for many things:
17+
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
25+
26+They work correctly in all possible ways with the RTL:
27+
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.
37+
38+Management operators, when implemented, are called implicitly at various times. For example:
39+
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.
45+
46+## Initialize
47+
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.
51+
52+``` pascal
53+program 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;
77+
78+var
79+ a, b: PRec;
80+
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.
89+```
90+
91+## Finalize
92+
93+Finalize is called when a record goes out of scope.
94+
95+It is useful for automatic custom finalization code.
96+
97+``` pascal
98+program 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.
130+```
131+
132+## AddRef
133+
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.
137+
138+``` pascal
139+program 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.
169+```
170+
171+## Copy
172+
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:
178+
179+``` pascal
180+uses
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+```
214+
215+This will output:
216+
217+ Normal record type with a dynamic array
218+ R1.Arr = [1,2,3]
219+ R2.Arr = [1,2,3]
220+ After R2.Arr[0] := 666
221+ R1.Arr = [666,2,3]
222+ 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:
227+
228+``` pascal
229+function CopyFrom(Src: TRec): TRec;
230+begin
231+ Result.Arr := Copy(Src.Arr);
232+end;
233+```
234+
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
240+{$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.
279+```
280+
281+This will output:
282+
283+ Managed record type with a dynamic array and class operator Copy
284+ M1.Arr = [1,2,3]
285+ M2.Arr = [1,2,3]
286+ After M2.Arr[0] := 666
287+ M1.Arr = [1,2,3]
288+ M2.Arr = [666,2,3]
289+
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.
291+
292+## Example of using Initialize and Finalize
293+
294+``` pascal
295+unit 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.
332+```
333+
334+How to use it
335+
336+``` pascal
337+procedure ExtractionResultTests.ObjectHandlerTest;
338+var
339+ a: TRow;
340+ ah: TObjectHandler;
341+begin
342+ a := TRow.Create;
343+ ah.obj := a;
344+end;
345+```
346+
347+In 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.
348+
349+---
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

Back to history