Why is this code working and how is Delphi instantiating a class in this case?

Viewed 110

I'm doing the Delphi track at exercism, and following how Delphi generates code for a form, answered one of the basic questions like this:

unit uLeap;

interface

type
  TSYear = class
    public
      { public declarations here }
      function isLeap(y: integer): boolean;
  end;

var
TYear: TSYear;

implementation

function TSYear.isLeap(y: integer): boolean;
begin
  result := ((y mod 4) = 0) and (((y mod 400) = 0) or ((y mod 100) <> 0));
end;

end.

the code compiles without a single complaint, I can run it step by step, and the "isLeap" function is called from another unit several times this way:

procedure YearTest.year_divisible_by_4_not_divisible_by_100_leap_year;
begin
  assert.IsTrue(TYear.IsLeap(1996), 'Expected ''true'', 1996 is a leap year.');
end;
...

I've never explicitly created the instance of the class, but it seems as if Delphi is doing it somewhere, maybe when declaring TYear? Is that a valid way?

Despite passing all the tests the code was rejected because it isn't done the conventional way. I'll surely end up doing it differently to have it accepted, but, besides the bad naming, why is this working? Would this code cause problems somewhere I can't see in this simple example?

1 Answers

I've never explicitly created the instance of the class, but it seems as if Delphi is doing it somewhere, maybe when declaring TYear?

No, Delphi is NOT automatically creating an instance of you. When you declare a variable of a class type, it is simply a pointer variable that can be made to point at a valid instance. But you must always create this instance yourself, and save the pointer in the variable:

SYear := TSYear.Create; // create a `TSYear` object and save its address in `SYear`

Is that a valid way?

No.

[W]hy is this working?

Because you are lucky: The isLeap function doesn't access any fields on the class instance.

Would this code cause problems somewhere I can't see in this simple example?

If the function had been using any fields in the class instance, you would have ended up with an AV if lucky and memory corruption if unlucky.

The solution is either to create an instance and use it:

SYear := TSYear.Create;
try
  ShowMessage(BoolToStr(SYear.IsLeap(2000), True));
finally
  SYear.Free;
end;

Or, since you clearly don't need any instance variables to determine if a year is a leap year or not, it is better to make this a class method:

type
  TSYear = class
  public
    class function IsLeap(AYear: Integer): Boolean; static;
  end;

This way, it can be called without any class instance: TSYear.IsLeap(2000). Notice that TSYear is the class (type) name, not a variable of this type.

Please see the documentation for a great conceptual introduction to all these concepts.

Related