Difference between revisions of "management operators"
(wikify, ie don't use HTML br tag) |
(removed some inverted commas so that the search engine can find the term Advanced Record) |
||
(2 intermediate revisions by one other user not shown) | |||
Line 1: | Line 1: | ||
− | == Management operators == | + | == Management operators feature == |
− | From Free Pascal version 3.1.1 onwards, there is a new language feature called management operators for | + | From Free Pascal version 3.1.1 onwards, there is a new language feature called management operators for extended or advanced records. |
The new operators are: Initialize, Finalize, AddRef and Copy. | The new operators are: Initialize, Finalize, AddRef and Copy. | ||
Line 40: | Line 40: | ||
* For parameters for routines - AddRef/Finalize/none - this depends on modifiers like var / constref / const. | * For parameters for routines - AddRef/Finalize/none - this depends on modifiers like var / constref / const. | ||
− | + | == Initialize == | |
The initialize operator is called directly after stack (or heap) memory allocation for a record happens. | The initialize operator is called directly after stack (or heap) memory allocation for a record happens. | ||
Line 82: | Line 82: | ||
end.</syntaxhighlight> | end.</syntaxhighlight> | ||
− | + | == Finalize == | |
Finalize is called when a record goes out of scope. | Finalize is called when a record goes out of scope. | ||
Line 120: | Line 120: | ||
end.</syntaxhighlight> | end.</syntaxhighlight> | ||
− | + | == AddRef == | |
AddRef is called after the contents of a record have been duplicated by a bitwise copy (for example '''after''', not during, an assigment.) | AddRef is called after the contents of a record have been duplicated by a bitwise copy (for example '''after''', not during, an assigment.) | ||
Line 140: | Line 140: | ||
TRec = record | TRec = record | ||
I: Integer; | I: Integer; | ||
− | class operator AddRef(var aRec: TRec) | + | class operator AddRef(var aRec: TRec); |
end; | end; | ||
− | class operator TRec.AddRef(var aRec: TRec) | + | class operator TRec.AddRef(var aRec: TRec); |
begin | begin | ||
WriteLn('Just to let you know: maybe you can do lifetime management here..'); | WriteLn('Just to let you know: maybe you can do lifetime management here..'); | ||
Line 156: | Line 156: | ||
end.</syntaxhighlight> | end.</syntaxhighlight> | ||
− | + | == Copy == | |
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. | 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. | ||
Line 163: | Line 163: | ||
There is a (complex) example in [https://gitlab.com/freepascal.org/fpc/source/-/blob/main/tests/test/tmoperator8.pp] within the FPC sources. | There is a (complex) example in [https://gitlab.com/freepascal.org/fpc/source/-/blob/main/tests/test/tmoperator8.pp] within the FPC sources. | ||
− | == | + | == Example of using Initialize and Finalize == |
<syntaxhighlight lang="pascal"> | <syntaxhighlight lang="pascal"> |
Latest revision as of 05:15, 28 November 2022
Management operators feature
From Free Pascal version 3.1.1 onwards, there is a new language feature called management operators for extended or advanced records.
The new operators are: Initialize, Finalize, AddRef and Copy.
These are a fairly unique feature, and are called "management operators" because:
- Each record, even non-managed or empty, that implements any management operators becomes a managed type.
- 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.
Management operators have no result type as opposed to normal operators, and work via a very simple VMT.
Thanks to this, it is possible to combine management operators with all low-level RTL functions, such as InitializeArray / FinalizeArray / etc.
Management operators can be used for many things:
- More granularly controlling the lifetimes of simple value types / primitives
- Implementing "nullable" value types
- Custom ARC implementations
- A very fast RTTI.TValue implementation
- As a replacement for manually-called Init/Done record methods like the popular "mORMot" library uses for many types (for example in SynCommons.TSynLocker).
- Auto init/finit for pointers/classes/simple types or anything else we have in Pascal.
- Much more
They work correctly in all possible ways with the RTL:
- New (Initialize).
- Dispose (Finalize).
- Initialize (Initialize).
- Finalize (Finalize).
- InitializeArray (Initialize).
- FinalizeArray (Finalize).
- SetLength (Initialize/Finalize).
- Copy (AddRef).
- RTTI.IsManaged.
Management operators, when implemented, are called implicitly at various times. For example:
- Global variables (Initialize/Finalize).
- Local variables (Initialize/Finalize).
- For fields inside records, objects or classes (Initialize/Finalize).
- Variable assignment (Copy).
- For parameters for routines - AddRef/Finalize/none - this depends on modifiers like var / constref / const.
Initialize
The initialize operator is called directly after stack (or heap) memory allocation for a record happens.
It allows for custom automatic initialization code.
program TestInitialize;
{$if FPC_FULLVERSION < 30101}
{$ERROR this demo needs version 3.1.1}
{$endif}
{$mode delphi}
type
PRec = ^TRec;
TRec = record
I: Integer;
class operator Initialize(var aRec: TRec);
end;
class operator TRec.Initialize(var aRec: TRec);
begin
aRec := Default(TRec); // initialize to default
end;
procedure PrintTRec(r: PRec);
begin
WriteLn('Initialized TRec field i: ', r^.I); // should always be zero, stack or heap
end;
var
a, b: PRec;
begin
New(a);
New(b); // standard "new" does not initialize, but now it does!
PrintTRec(a);
PrintTRec(b);
Dispose(a);
Dispose(b);
end.
Finalize
Finalize is called when a record goes out of scope.
It is useful for automatic custom finalization code.
program TestFinalize;
{$if FPC_FULLVERSION < 30101}
{$ERROR this demo needs version 3.1.1}
{$endif}
{$mode delphi}
type
PRec = ^TRec;
TRec = record
I: Integer;
class operator Finalize(var aRec: TRec);
end;
class operator TRec.Finalize(var aRec: TRec);
begin
WriteLn('Just to let you know: I am finalizing..');
end;
var
a, b: PRec;
c: array of TRec;
begin
New(a);
New(b);
Dispose(a);
Dispose(b);
WriteLn('Just before program termination this will also be finalized');
SetLength(c, 4);
end.
AddRef
AddRef is called after the contents of a record have been duplicated by a bitwise copy (for example after, not during, an assigment.)
By itself it does not do any lifetime management, but you can use it to implement it. See also Copy.
program TestAddref;
{$if FPC_FULLVERSION < 30101}
{$ERROR this demo needs version 3.1.1}
{$endif}
{$mode delphi}
uses
SysUtils;
type
PRec = ^TRec;
TRec = record
I: Integer;
class operator AddRef(var aRec: TRec);
end;
class operator TRec.AddRef(var aRec: TRec);
begin
WriteLn('Just to let you know: maybe you can do lifetime management here..');
end;
var
a, b: array of TRec;
begin
SetLength(a, 4);
b := Copy(a);
end.
Copy
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.
todo: add a simple example!
There is a (complex) example in [1] within the FPC sources.
Example of using Initialize and Finalize
unit UResourceHandlers;
{$if FPC_FULLVERSION < 30101}
{$ERROR this demo needs version 3.1.1}
{$endif}
{$mode delphi}
interface
uses
Classes, SysUtils;
type
{ TObjectHandler }
TObjectHandler = record
obj: TObject;
class operator Initialize(var hdl: TObjectHandler);
class operator Finalize(var hdl: TObjectHandler);
end;
implementation
{ TObjectHandler }
class operator TObjectHandler.Initialize(var hdl: TObjectHandler);
begin
hdl.obj := nil;
end;
class operator TObjectHandler.finalize(var hdl: TObjectHandler);
begin
FreeAndNil(hdl.obj);
end;
end.
How to use it
procedure ExtractionResultTests.ObjectHandlerTest;
var
a: TRow;
ah: TObjectHandler;
begin
a := TRow.Create;
ah.obj := a;
end;
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.