Нужны ли конструкторы записи Delphi?

СИТУАЦИЯ

Я изучаю "Больше кодирования в Delphi" Ником Ходжесом, и он использует запись TFraction для объяснения перегрузки операторов. Я сам написал эту запись:

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;

Конечно, мне пришлось создать конструктор, потому что в Q (рациональных) я должен иметь знаменатель, который не равен нулю.

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;

ПРОБЛЕМА

Так как оператор перегружает return a TFraction, я собираюсь определить такую ​​операцию:

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;

Как вы можете видеть здесь, я создаю tmp, который возвращается из функции.

Когда я прочитал книгу Марко Канту, он использовал другой подход:

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;

Я сделал несколько тестов, и я вижу, что оба дают мне правильный результат, но есть кое-что, что я не могу понять. В первом подходе я объявляю tmp, а затем я вызываю конструктор, чтобы я мог вернуть TFraction. Во втором подходе я вместо этого ничего не создаю, потому что записи имеют автоматический конструктор. Фактически, документация гласит, что:

Записи создаются автоматически, используя аргумент no-argument по умолчанию конструктор, но классы должны быть явно построены. Потому как записи имеют конструктор без аргументов по умолчанию, любые пользовательские конструктор записи должен иметь один или несколько параметров.

Здесь у меня есть пользовательский конструктор записи. Итак:

  • Является ли вызов конструктора на tmp первого подхода не нужен? Если я хочу вызвать Reduce (это процедура), мне нужно создать переменную. Является ли Result просто возвращением копии tmp без создания чего-либо?

  • Во втором подходе есть Result.aNumerator и Result.aDenominator параметры автоматического созданного конструктора?

Ответ 1

Конструктор записи не является чем-то волшебным. Это просто метод экземпляра, как любой другой. Вы пишете:

tmp := TFraction.Create(...);

Но вы можете так же писать так:

tmp.Create(...);

Мне лично не кажется, что это особенно полезно, потому что я использую конструктор, вызывающий семантику для классов, которые выделяют и инициализируют память по умолчанию, а затем вызывают метод конструктора.

И особенно второй вариант решает со мной, потому что это похоже на классическую ошибку, которую начинающие программисты Delphi делают при запуске и пытаются создать экземпляр класса. Этот код не подходит, если TFraction был классом, но для записи это нормально.

Это я, я бы избавился от конструктора записи и вместо этого использовал статическую функцию класса, которая вернула новый чеканный экземпляр вашего типа записи. Мое соглашение - назвать такие вещи New. Но это вопросы личного предпочтения.

Если вы сделали это, оно будет объявлено следующим образом:

class function New(aNumerator, aDenominator: Integer): TFraction; static;

Он будет реализован следующим образом:

class function TFraction.New(aNumerator, aDenominator: Integer): TFraction;
begin
  Result.aNumerator := ...;
  Result.aDenominator := ...;
end;

Затем вы вызываете это следующим образом:

frac := TFraction.New(num, denom);

Но, как я уже сказал, это вопрос предпочтения. Если вам нравятся конструкторы записи, не стесняйтесь придерживаться их.


Вы спрашиваете, можете ли вы пропустить конструктор. Что касается распределения записи, да, вы можете пропустить ее. С точки зрения запуска кода в конструкторе, вы можете это определить. Вы хотите, чтобы этот код выполнялся или не выполнялся?

Если вы хотите, чтобы этот код выполнялся, но не хотите использовать временную переменную, вы можете написать код следующим образом:

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;

Или, если вы предпочитаете статическую функцию класса, это будет:

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;