Difference between revisions of "management operators"

From Free Pascal wiki
Jump to navigationJump to search
Line 126: Line 126:
 
By itself it does not any lifetime management, but you can use it to implement it. See also Copy.
 
By itself it does not any lifetime management, but you can use it to implement it. See also Copy.
 
example:
 
example:
<syntaxhighlight>
+
<syntaxhighlight>{$if FPC_FULLVERSION < 30101}
{$if FPC_FULLVERSION < 30101}{$ERROR this demo needs version 3.1.1}{$endif}
+
{$ERROR this demo needs version 3.1.1}
 +
{$endif}
 
{$mode delphi}{$macro on}
 
{$mode delphi}{$macro on}
 +
 
program testaddref;
 
program testaddref;
  
uses sysutils;
+
uses
 +
  SysUtils;
  
 
type
 
type
Line 137: Line 140:
  
 
   TRec = record
 
   TRec = record
     I : Integer;
+
     I: Integer;
     class operator AddRef(var aRec: TRec);
+
     class operator AddRef(var aRec: TRec): T;
 
   end;
 
   end;
  
   class operator TRec.Addref(var aRec: TRec);
+
   class operator TRec.Addref(var aRec: TRec): T;
 
   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 147: Line 150:
  
 
var
 
var
   a,b:array of Trec;
+
   a, b: array of Trec;
+
 
 
begin
 
begin
 
   setlength(a, 4);
 
   setlength(a, 4);

Revision as of 03:09, 2 November 2019

Management Operators

From Free Pascal version 3.1.1 onwards, there is a new language feature called management operators for 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, with management operator 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.
  • 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 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 in mORMot for many types (for example 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.


Managements operators are often called implicitly, 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 after memory allocation for a record and called after the internal compiler call to recordrtti(data,typeinfo,@int_initialize); It allows automatic initialization for a record. A simple example is:

{$if FPC_FULLVERSION < 30101}
{$ERROR this demo needs version 3.1.1}
{$endif}
{$mode delphi}{$macro on}

program TestInitialize;

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 PrintRec(r: PRec);
  begin
    WriteLn('Initialized TRec field i: ', r^.I = 0);  // 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 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:

{$if FPC_FULLVERSION < 30101}
{$ERROR this demo needs version 3.1.1}
{$endif}
{$mode delphi}{$macro on}

program testfinalize;

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 has been duplicated by copying the contents byte by byte and is called *after* FPC internal call recordrtti(data,typeinfo,@int_addref); By itself it does not any lifetime management, but you can use it to implement it. See also Copy. example:

{$if FPC_FULLVERSION < 30101}
{$ERROR this demo needs version 3.1.1}
{$endif}
{$mode delphi}{$macro on}

program testaddref;

uses
  SysUtils;

type
  PRec = ^TRec;

  TRec = record
    I: Integer;
    class operator AddRef(var aRec: TRec): T;
  end;

  class operator TRec.Addref(var aRec: TRec): T;
  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 simple example! There is a (complex) example in /tests/test/tmoperator8.pas

A Simple example of use

Simple resource handler

 unit UResourceHandlers;  

 {$if FPC_FULLVERSION < 30101}{$ERROR this demo needs version 3.1.1}{$endif}  
 {$mode delphi} 
 {$modeswitch advancedrecords}  

 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 or TCriticalSeccions i guess.