SITUATION
I am studying "More Coding in Delphi" by Nick Hodges, and he is using a TFraction
record to explain operator overloading. I have written by myself this record:
type
TFraction = record
strict private
aNumerator: integer;
aDenominator: integer;
function GCD(a, b: integer): integer;
public
constructor Create(aNumerator: integer; aDenominator: integer);
procedure Reduce;
class operator Add(fraction1, fraction2: TFraction): TFraction;
class operator Subtract(fraction1, fraction2: TFraction): TFraction;
//... implicit, explicit, multiply...
property Numerator: integer read aNumerator;
property Denominator: integer read aDenominator;
end;
Of course, I had to create a constructor because in Q (rationals) I must have a denominator that is not equal to zero.
constructor TFraction.Create(aNumerator, aDenominator: integer);
begin
if (aDenominator = 0) then
begin
raise Exception.Create('Denominator cannot be zero in rationals!');
end;
if ( (aNumerator < 0) or (aDenominator < 0) ) then
begin
Self.aNumerator := -aNumerator;
Self.aDenominator := -aDenominator;
end
else
begin
Self.aNumerator := aNumerator;
Self.aDenominator := aDenominator;
end;
end;
PROBLEM
Since the operator overloads return a TFraction
, I am going to define an operation like this:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
var
tmp: TFraction;
begin
//simple algorithm of the sum
tmp := TFraction.Create(fraction1.Numerator*fraction2.Denominator+fraction1.Denominator*fraction2.Numerator, fraction1.Denominator*fraction2.Denominator);
tmp.Reduce;
//return the result
Result := tmp;
end;
As you can see here, I am creating a tmp
that is returned from the function.
When I read Marco Cantu's book, he used another approach:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
Result.aNumerator := (fraction1.Numerator*fraction2.Denominator+fraction1.Denominator*fraction2.Numerator);
Result.aDenominator := fraction1.Denominator*fraction2.Denominator;
end;
I have made some tests, and I see that both give me the correct result, but there is something that I cannot understand. In the first approach, I am declaring tmp and then I call the constructor so I can return a TFraction
. In the second approach, I am instead not creating anything because records have an automatic constructor. The documentation, in fact, says that:
Records are constructed automatically, using a default no-argument constructor, but classes must be explicitly constructed. Because records have a default no-argument constructor, any user-defined record constructor must have one or more parameters.
Here I have a user-defined record constructor. So:
Is the constructor call on tmp
of the first approach not needed? If I want to call Reduce
(which is a procedure), I need to create a variable. Is the Result
just returning a copy of tmp
without creating anything?
In the second approach, are Result.aNumerator
and Result.aDenominator
the parameters of the automatic created constructor?
A record constructor isn't anything magical. It's just an instance method like any other. You write:
tmp := TFraction.Create(...);
But you may equally well write it like this:
tmp.Create(...);
I personally find neither to be especially useful because I am used to constructor calling semantics for classes which allocate and default initialise memory, and then call the constructor method.
And especially the second variant grates with me because that looks like the classic mistake that novice Delphi programmers make when starting out and trying to create an instance of a class. That code would be no good if TFraction
were a class, but for a record it is fine.
Were it me I would get rid of the record constructor and instead use a static class function that returned a newly minted instance of your record type. My convention is to name such things New
. But these are matters of personal preference.
If you did that it would be declared like this:
class function New(aNumerator, aDenominator: Integer): TFraction; static;
It would be implemented like this:
class function TFraction.New(aNumerator, aDenominator: Integer): TFraction;
begin
Result.aNumerator := ...;
Result.aDenominator := ...;
end;
You would then call it like this:
frac := TFraction.New(num, denom);
But as I said, that's a matter of preference. If you like record constructors, feel free to stick with them.
You ask whether or not you can skip the constructor. In terms of allocation of the record, yes you can skip it. In terms of running the code in the constructor, only you can determine that. Do you want that code to execute or not?
If you wish that code to be executed, but don't want to use a temporary variable, then you can write the code like this:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
Result.Create(
fraction1.Numerator*fraction2.Denominator + fraction1.Denominator*fraction2.Numerator,
fraction1.Denominator*fraction2.Denominator
);
Result.Reduce;
end;
Or if you preferred a static class function it would be:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
Result := TFraction.New(
fraction1.Numerator*fraction2.Denominator + fraction1.Denominator*fraction2.Numerator,
fraction1.Denominator*fraction2.Denominator
);
Result.Reduce;
end;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With