Sign in Join
Wiki copytemporary

management operators

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:

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:

They work correctly in all possible ways with the RTL:

Management operators, when implemented, are called implicitly at various times. For example:

Initialize

The initialize operator is called directly after stack (or heap) memory allocation for a record happens.

It allows for custom automatic initialization code.

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

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

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

This is very helpfull if your record contains e.g. a dynamic array.\

Consider the following program:

pascal
uses
  SysUtils;

type
  TArr = array of integer;
  TRec = record
    Arr: TArr;
  end;

function ArrToString(Arr: TArr): String;
var
  X: Integer;
begin
  Result := '';
  for X in Arr do Result := Result + IntToStr(X) + ',';
  if Result <> '' then Delete(Result, Length(Result), 1);
  Result := '[' + Result + ']';
end;

var
  R1, R2: TRec;

begin
  writeln('Normal record type with a dynamic array');
  R1.Arr := [1,2,3];
  R2 := R1;
  writeln('R1.Arr = ',ArrToString(R1.Arr));  //[1,2,3]
  writeln('R2.Arr = ',ArrToString(R2.Arr));  //[1,2,3]
  writeln('After R2.Arr[0] := 666');
  R2.Arr[0] := 666;  
  writeln('R1.Arr = ',ArrToString(R1.Arr)); //[666,2,3]
  writeln('R2.Arr = ',ArrToString(R2.Arr)); //[666,2,3]
end.

This will output:

pascal
Normal record type with a dynamic array
R1.Arr = [1,2,3]
R2.Arr = [1,2,3]
After R2.Arr[0] := 666
R1.Arr = [666,2,3]
R2.Arr = [666,2,3]

Notice that changing R2.Arr changes R1.Arr as well, since both the arrays point to the same memory location.
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.
If you want to avoid this, you could write a workaround like:

pascal
function CopyFrom(Src: TRec): TRec;
begin
  Result.Arr := Copy(Src.Arr);
end;

And then always use R2 := CopyFrom(R1) instead of R2 := R1.\

Or you can solve this problem using the Copy management operator:

pascal
{$modeswitch advancedrecords}
uses
  SysUtils;

type
  TManagedRec = record
    Arr: TArr;
    class operator Copy(constref aSrc: TManagedRec; var aDst: TManagedRec);
  end;

{ TMangedRec }
class operator TManagedRec.Copy(constref aSrc: TManagedRec; var aDst: TManagedRec);
begin
  aDst.Arr := Copy(aSrc.Arr);
end;

function ArrToString(Arr: TArr): String;
var
  X: Integer;
begin
  Result := '';
  for X in Arr do Result := Result + IntToStr(X) + ',';
  if Result <> '' then Delete(Result, Length(Result), 1);
  Result := '[' + Result + ']';
end;

var
  M1, M2: TManagedRec;
begin
  writeln('Managed record type with a dynamic array and class operator Copy');
  M1.Arr := [1,2,3];
  M2 := M1;
  writeln('M1.Arr = ',ArrToString(M1.Arr)); //[1,2,3]
  writeln('M2.Arr = ',ArrToString(M2.Arr)); //[1,2,3]
  writeln('After M2.Arr[0] := 666');
  M2.Arr[0] := 666; 
  writeln('M1.Arr = ',ArrToString(M1.Arr)); //[1,2,3]
  writeln('M2.Arr = ',ArrToString(M2.Arr)); //[666,2,3]
end.

This will output:

pascal
Managed record type with a dynamic array and class operator Copy
M1.Arr = [1,2,3]
M2.Arr = [1,2,3]
After M2.Arr[0] := 666
M1.Arr = [1,2,3]
M2.Arr = [666,2,3]

There is also a (more complex) example in 1 within the FPC sources.

Example of using Initialize and Finalize

pascal
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

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


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.

Revision 1, last changed by WikiArchive .

Talk about this page

Nobody has opened a bench for this page yet. Sign in to open one.