On the operator overloading in Delphi

The operator overloading in Delphi records is straightforward if a record type does not contain fields which reference heap objects. To illustrate the problem which heap references arise let us consider the following (incorrect) example:

program DelphiDemo;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  Adder = record
  private
    FRef: PInteger;
    function GetMemory: Integer;
    procedure SetMemory(AValue: Integer);
  public
    procedure Init(AValue: Integer = 0);
    procedure Done;
    class operator Add(const A, B: Adder): Adder;
    property Memory: Integer read GetMemory write SetMemory;
  end;

{ Adder }

class operator Adder.Add(const A, B: Adder): Adder;
begin
// !!! Memory leak
  New(Result.FRef);
  Result.Memory:= A.Memory + B.Memory;
end;

procedure Adder.Done;
begin
  Dispose(FRef);
end;

function Adder.GetMemory: Integer;
begin
  Result:= FRef^;
end;

procedure Adder.Init(AValue: Integer);
begin
  New(FRef);
  FRef^:= AValue;
end;

procedure Adder.SetMemory(AValue: Integer);
begin
  FRef^:= AValue;
end;

procedure Test;
var
  A, B, C: Adder;

begin
  A.Init(1);
  B.Init(2);
  C.Init();
  C:= A + B;
  Writeln(C.Memory);
  C.Done;
  B.Done;
  A.Done;
end;

begin
  ReportMemoryLeaksOnShutdown:= True;
  try
    Test;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

The line #59 (C:= A + B) in the above program works as follows:

  • A temporary Result record is pushed on stack
  • The temporary record receives the sum A + B
  • The temporary record is assigned (by shallow copying) to the C variable
  • The temporary record is popped from stack

It would work fine if Adder did not reference a heap data; the FRef of the Adder record field makes things complicated. You should always initialize FRef field for every Adder instance but you can’t finalize it for a temporary record that is created on line #59. The only way to solve the memory leak issue in the above code is to comment out the line #58, but it will not work for more complicated right-hand side expressions and it is not a solid approach anyway.

The correct solution involves using a type with automatic memory management instead of a simple pointer. Here is a solution that uses interface:

program DelphiDemo2;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes;

type
  IAdder = interface
    function GetMemory: Integer;
    procedure SetMemory(AValue: Integer);
  end;

  TAdderRef = class(TInterfacedObject, IAdder)
  private
    FMemory: Integer;
    function GetMemory: Integer;
    procedure SetMemory(AValue: Integer);
  end;

  Adder = record
  private
    FRef: IAdder;
    function GetMemory: Integer;
    procedure SetMemory(AValue: Integer);
  public
    procedure Init(AValue: Integer = 0);
    procedure Done;
    class operator Add(const A, B: Adder): Adder;
    property Memory: Integer read GetMemory write SetMemory;
  end;

{ TAdderRef }

function TAdderRef.GetMemory: Integer;
begin
  Result:= FMemory;
end;

procedure TAdderRef.SetMemory(AValue: Integer);
begin
  FMemory:= AValue;
end;

{ Adder }

class operator Adder.Add(const A, B: Adder): Adder;
begin
  Result.FRef:= TAdderRef.Create;
  Result.Memory:= A.Memory + B.Memory;
end;

procedure Adder.Init(AValue: Integer);
begin
  FRef:= TAdderRef.Create;
  FRef.SetMemory(AValue);
end;

procedure Adder.Done;
begin
  FRef:= nil;
end;

function Adder.GetMemory: Integer;
begin
  Result:= FRef.GetMemory;
end;

procedure Adder.SetMemory(AValue: Integer);
begin
  FRef.SetMemory(AValue);
end;

procedure Test;
var
  A, B, C: Adder;

begin
  A.Init(1);
  B.Init(2);
//  C.Init();
  C:= A + B;
  Writeln(C.Memory);
//  C.Done;
//  B.Done;
//  A.Done;
end;

begin
  ReportMemoryLeaksOnShutdown:= True;
  try
    Test;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

A nice side effect of the above approach is that you need not initialize or finalize the FRef fields manually anymore (though you can still do it). Some lines in the Test procedure above were commented out because they are not needed, but they can be uncommented and the code will remain correct – automatic memory management of interfaces takes care of it.

It is very interesting to know how the problem discussed above is solved in C++. The standard C++ approach is totally different – it involves overloading the assignment operator (a feature which Delphi does not support; Delphi allows to overload only conversionImplicit, Explicit operators) and writing a copy constructor (another concept absent in Delphi object model). I am planning to discuss it later.

Advertisements

4 thoughts on “On the operator overloading in Delphi

  1. This has reference semantics which renders it pretty much useless.

    var
      A, B: Adder;
    .....
    A.Init(1);
    B := A;
    B.Init(3);
    // now A.Memory = 3
    

    This could be solved if Adder is made to be immutable.

    • That is true. You can use

      (sourcecode language="delphi")
        ...
      (/sourcecode) 

      tags in square brackets instead of round ones to publish delphi code on wordpress blogs, or simply (code) … (/code) also in square brackets. More details here

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s