Difference between revisions of "management operators"

From Lazarus wiki
Jump to navigationJump to search
(removed some inverted commas so that the search engine can find the term Advanced Record)
 
(48 intermediate revisions by 3 users 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 advanced records.<br>
+
From Free Pascal version 3.1.1 onwards, there is a new language feature called management operators for extended or advanced records.
<br>
+
 
The new operators are: Initialize, Finalize, AddRef and Copy.<br>
+
The new operators are: Initialize, Finalize, AddRef and Copy.
<br>
+
 
 
These are a fairly unique feature, and are called "management operators" because:
 
These are a fairly unique feature, and are called "management operators" because:
<br>
+
 
* Each record, even non-managed or empty, with management operator becomes a managed type.<br>
+
* 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.<br>
+
* 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.<br>
+
 
* A simple virtual method table is generated for the management operators. Thanks to this it is possible to combine management operators with all RTL functions, such as InitializeArray / FinalizeArray / etc.
+
Management operators have no result type as opposed to normal operators, and work via a very simple VMT.
<br>
+
 
Management Operators can be used for many things:
+
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
 
* More granularly controlling the lifetimes of simple value types / primitives
 
* Implementing "nullable" value types
 
* Implementing "nullable" value types
 
* Custom ARC implementations
 
* Custom ARC implementations
 
* A very fast RTTI.TValue implementation
 
* A very fast RTTI.TValue implementation
* As a replacement for manually-called Init/Done record methods like in mORMot for many types (for example SynCommons.TSynLocker).
+
* 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.
 
* Auto init/finit for pointers/classes/simple types or anything else we have in Pascal.
 
* Much more
 
* Much more
<br>
+
 
 
They work correctly in all possible ways with the RTL:  
 
They work correctly in all possible ways with the RTL:  
 
* New (Initialize).
 
* New (Initialize).
Line 30: Line 32:
 
* Copy (AddRef).
 
* Copy (AddRef).
 
* RTTI.IsManaged.
 
* RTTI.IsManaged.
<br>
+
 
Management operators are often called implicitly, for example:
+
Management operators, when implemented, are called implicitly at various times. For example:
 
* Global variables (Initialize/Finalize).
 
* Global variables (Initialize/Finalize).
 
* Local variables (Initialize/Finalize).
 
* Local variables (Initialize/Finalize).
 
* For fields inside records, objects or classes (Initialize/Finalize).
 
* For fields inside records, objects or classes (Initialize/Finalize).
 
* Variable assignment (Copy).
 
* Variable assignment (Copy).
* 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.
<br>
+
 
=== Initialize ===
+
== Initialize ==
The initialize operator is called after memory allocation for a record and called after the internal compiler call to recordrtti(data,typeinfo,@int_initialize);
+
The initialize operator is called directly after stack (or heap) memory allocation for a record happens.
It allows automatic initialization for a record.
+
 
A simple example is:
+
It allows for custom automatic initialization code.
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
 +
program TestInitialize;
 +
 
{$if FPC_FULLVERSION < 30101}
 
{$if FPC_FULLVERSION < 30101}
 
{$ERROR this demo needs version 3.1.1}
 
{$ERROR this demo needs version 3.1.1}
 
{$endif}
 
{$endif}
{$mode delphi}{$macro on}
+
{$mode delphi}
 
 
program TestInitialize;
 
  
 
type
 
type
Line 63: Line 65:
 
   end;
 
   end;
  
   procedure PrintRec(r: PRec);
+
   procedure PrintTRec(r: PRec);
 
   begin
 
   begin
     WriteLn('Initialized TRec field i: ', r^.I = 0);  // should always be zero, stack or heap
+
     WriteLn('Initialized TRec field i: ', r^.I);  // should always be zero, stack or heap
 
   end;
 
   end;
  
Line 80: Line 82:
 
end.</syntaxhighlight>
 
end.</syntaxhighlight>
  
<br>
+
== Finalize ==
 +
Finalize is called when a record goes out of scope.
  
=== Finalize ===
+
It is useful for automatic custom finalization code.
Finalize is called when a record goes out of scope and called before the internal call to recordrtti(data,typeinfo,@int_finalize);
 
It is useful for automatic custom finalization code. A simple example looks like:
 
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
 +
program TestFinalize;
 +
 
{$if FPC_FULLVERSION < 30101}
 
{$if FPC_FULLVERSION < 30101}
 
{$ERROR this demo needs version 3.1.1}
 
{$ERROR this demo needs version 3.1.1}
 
{$endif}
 
{$endif}
{$mode delphi}{$macro on}
+
{$mode delphi}
 
 
program testfinalize;
 
  
 
type
 
type
Line 98: Line 99:
 
   TRec = record
 
   TRec = record
 
     I: Integer;
 
     I: Integer;
     class operator finalize(var aRec: TRec);
+
     class operator Finalize(var aRec: TRec);
 
   end;
 
   end;
  
   class operator TRec.finalize(var aRec: TRec);
+
   class operator TRec.Finalize(var aRec: TRec);
 
   begin
 
   begin
     writeln('Just to let you know: I am finalizing..');
+
     WriteLn('Just to let you know: I am finalizing..');
 
   end;
 
   end;
  
 
var
 
var
 
   a, b: PRec;
 
   a, b: PRec;
   c: array of Trec;
+
   c: array of TRec;
  
 
begin
 
begin
Line 115: Line 116:
 
   Dispose(a);
 
   Dispose(a);
 
   Dispose(b);
 
   Dispose(b);
   writeln('Just before program termination this will also be finalized');
+
   WriteLn('Just before program termination this will also be finalized');
   Setlength(c, 4);
+
   SetLength(c, 4);
 
end.</syntaxhighlight>
 
end.</syntaxhighlight>
  
<br>
+
== 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.
 +
<syntaxhighlight lang="pascal">
 +
program TestAddref;
  
=== AddRef ===
+
{$if FPC_FULLVERSION < 30101}
AddRef is called after the contents of a record have been duplicated by a bitwise copy, and is called *after* the compiler-inserted internal call to "recordrtti(data,typeinfo,@int_addref)";
 
By itself it does not do any lifetime management, but you can use it to implement it. See also Copy.
 
example:
 
<syntaxhighlight lang="pascal">{$if FPC_FULLVERSION < 30101}
 
 
{$ERROR this demo needs version 3.1.1}
 
{$ERROR this demo needs version 3.1.1}
 
{$endif}
 
{$endif}
{$mode delphi}{$macro on}
+
{$mode delphi}
 
 
program testaddref;
 
  
 
uses
 
uses
Line 140: Line 140:
 
   TRec = record
 
   TRec = record
 
     I: Integer;
 
     I: Integer;
     class operator AddRef(var aRec: TRec): T;
+
     class operator AddRef(var aRec: TRec);
 
   end;
 
   end;
  
   class operator TRec.Addref(var aRec: TRec): T;
+
   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..');
 
   end;
 
   end;
  
 
var
 
var
   a, b: array of Trec;
+
   a, b: array of TRec;
  
 
begin
 
begin
   setlength(a, 4);
+
   SetLength(a, 4);
   b := copy(a);
+
   b := Copy(a);
 
end.</syntaxhighlight>
 
end.</syntaxhighlight>
  
=== Copy ===
+
== 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.
  
<br>
 
 
'''todo''': add a simple example!
 
'''todo''': add a simple example!
<br>
 
There is a (complex) example in "/tests/test/tmoperator8.pas" within the FPC sources.
 
  
=== A simple example of using management operators ===
+
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">
 
unit UResourceHandlers;
 
unit UResourceHandlers;
  
{$if FPC_FULLVERSION < 30101}{$ERROR this demo needs version 3.1.1}{$endif}
+
{$if FPC_FULLVERSION < 30101}
 +
{$ERROR this demo needs version 3.1.1}
 +
{$endif}
 
{$mode delphi}
 
{$mode delphi}
{$modeswitch advancedrecords}
 
  
 
interface
 
interface
Line 185: Line 185:
 
     obj: TObject;
 
     obj: TObject;
 
     class operator Initialize(var hdl: TObjectHandler);
 
     class operator Initialize(var hdl: TObjectHandler);
     class operator finalize(var hdl: TObjectHandler);
+
     class operator Finalize(var hdl: TObjectHandler);
 
   end;
 
   end;
  
Line 218: Line 218:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
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 or TCriticalSeccions i guess.
+
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.
  
 
[[Category:FPC]]
 
[[Category:FPC]]

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.