Why can I not use operator overloading for classes

2019-07-20 09:17发布

问题:

I'm having a problem with class operators in TDateTime.

type
  TDateTime = class(TObject)
  public
    class operator Add(a: TDateTime; b: TTimeSpan): TDateTime;
    class operator Subtract(a: TDateTime; b: TTimeSpan): TDateTime;
end;

implementation

class operator TDateTime.Add(a: TDateTime; b: TTimeSpan): TDateTime;
begin
  result := TDateTime.Create(a.Ticks + b.Ticks);
end;

class operator TDateTime.Subtract(a: TDateTime; b: TTimeSpan): TDateTime;
begin
  result := TDateTime.Create(a.Ticks - b.Ticks);
end;

The error occurred at 4th line

E2123 PROCEDURE, FUNCTION, PROPERTY, or VAR expected

I write this for Windows. But if it available only for .net and iOS, how can I do that for Windows?

回答1:

It is possible to use operator overloading only for records, not for classes in Delphi for Windows.
http://docwiki.embarcadero.com/RADStudio/XE4/en/Operator_Overloading_(Delphi)

Some useful explanations

P.S. TDateTime is bad name for user type.



回答2:

In desktop versions of Delphi (Windows, OSX), operator overloading is only available for records. For mobile versions of the compiler, ARC means that operator overloading is available for classes:

Operator Overloading for Classes

A very interesting side effect of using ARC memory management is that the compiler can handle the lifetime of temporary objects returned by functions. One specific case is that of temporary objects returned by operators. In fact, a brand new feature of the new Delphi compiler is the ability to define operators for classes, with the same syntax and model that has been available for records since Delphi 2006.

And if I recall correctly, operator overloading was available in the now discontinued .net compiler. This was again possible because the .net garbage collector could tidy up any temporary objects.

The doc excerpt above indicates why operator overloading is not possible with classes on non-ARC versions. Without some automatic lifetime management, temporary classes are leaked. On the other hand, temporary records can be created and destroyed in automatic storage on the stack.

That said, your somewhat mis-named type looks like it might be better as a value type in any case. So the solution to your problem is to:

  1. Give your type a better name, one that doesn't clash with a fundamental type.
  2. Change your type to be a record, that is a value type.


回答3:

As a side note:

You can use it on classes in the Delphi .NET compiler (which since then has been discontinued) as the .NET garbage collection will eventually remove the new object instances returned by the operators, and more recently on the ARC based Delphi ARM compilers (that also release memory based on reference counting).

To answer your question:

You cannot use it on classes in the native Delphi compilers that do not support ARC (currently the Intel based x86 and x64 compilers for Windows, OS X and iOS simulator do not have ARC support), as it would heavily leak memory because the operators would return new object instances that nobody would free.

With the introduction of ARC (currently the ARM based mobile platforms), you can as those compiler compile this fine.

Notes:

  • be careful redefining existing Delphi types like TDateTime
  • be aware you can only use the below on ARC compilers so you are writing highly platform dependent code
  • if you want to be cross platform, make it a value type like the TTimeStamp record is

In the code below (which only works for ARC), replace class with record do make it cross platform. It isn't by coincidence that the .NET framework has TimeSpan and DateTime (on which you might have based your code) are struct value types, not as class reference types.

To summarise, when writing cross platform code be aware of value and reference types and favour the record value type when using operator overloading.

type
  TTimeSpan = class
    Ticks: Longint;
  end;

  TDateTime = class
  public
    Ticks: Longint;
    class operator Add(a: TDateTime; b: TTimeSpan): TDateTime;
    class operator Subtract(a: TDateTime; b: TTimeSpan): TDateTime;
    constructor Create(aTicks: Longint);
  end;

class operator TDateTime.Add(a: TDateTime; b: TTimeSpan): TDateTime;
begin
  Result := TDateTime.Create(a.Ticks + b.Ticks);
end;

constructor TDateTime.Create(aTicks: Integer);
begin
  Ticks := aTicks;
end;

class operator TDateTime.Subtract(a: TDateTime; b: TTimeSpan): TDateTime;
begin
  Result := TDateTime.Create(a.Ticks - b.Ticks);
end;