Difference between revisions of "management operators"

From Lazarus wiki
Jump to navigationJump to search
Line 82: Line 82:
  
 
=== Finalize ===
 
=== Finalize ===
Finalize is called when a record goes out of scope and called before the internal call to recordrtti(data,typeinfo,@int_finalize);
+
Finalize is called when a record goes out of scope, *before* the internal compiler call to "recordrtti(data,typeinfo,@int_finalize);"<br>
It is useful for automatic custom finalization code. A simple example looks like:
+
<br>
 +
It is useful for automatic custom finalization code.
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
 
program TestFinalize;
 
program TestFinalize;

Revision as of 02:29, 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.


Management 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.

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 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, *before* the internal compiler call to "recordrtti(data,typeinfo,@int_finalize);"

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, and is called *after* the internal compiler 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.

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): 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 a simple example!

There is a (complex) example in "/tests/test/tmoperator8.pas" within the FPC sources.

A simple example of using Initialize and Finalize together

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.