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 conversion – Implicit, Explicit operators) and writing a copy constructor (another concept absent in Delphi object model). I am planning to discuss it later.
This has reference semantics which renders it pretty much useless.
This could be solved if Adder is made to be immutable.
That is true. You can use
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
Thanks for the tip about the language. I’m clueless with WP.
Ottimo, articolo davvero interessante, era proprio quello che cercavo! Grazie per lo spunto!
Pingback: Проблемы перегрузки операторов в Delphi | DelphiFeeds.ru 2.0