Lazarus / Delphi - Create vs TSomeClass.Create внутри конструктора - почему это вызывает проблемы? - PullRequest
1 голос
/ 18 мая 2019

Highlight

Этот конструктор:

constructor TCoords.Create(const AX, AY: Integer);
begin
    TCoords.Create(Point(AX, AY));
end;

подтвержден сбой в Linux Lazarus 2 и Windows Delphi XE6.

Может ли это быть ошибкой?


Я новичок в ООП в Lazarus / Delphi, извините, возможно, ошибка новичка. Спасибо.

Я не могу понять, почему следующая последовательность Lazarus / (Delphi-like) custom, очень простой, объект не будет работать. Я пытаюсь отлаживать это уже часами, с тех пор я нашел:

Что работает:

Вызов одного конструктора без аргумента и прямой вызов конструктора с аргументом TPoint.

Что не:

Называя это:

constructor Create(const AX, AY: Integer);

Однако я обнаружил, что это сработает - Но только если , если вызывается без имени класса внутри конструктора. Почему это вызывает проблемы?


Объявления

// WORKS - this creates instance of TCoords initialized to PointOutOfReach
constructor Create; reintroduce;
// WORKS - this creates instance of TCoords initialized to user coordinates
constructor Create(const ACoords: TPoint);
// DOES NOT WORK, strangely returns Point(0, 0), if called with class name
// WORKS only if called without class name - confusing or error on my side?
constructor Create(const AX, AY: Integer);

Звонки

// OK - WORKING
NewCoords := TCoords.Create;
NewCoords.X:=12;
NewCoords.Y:=120;
ShowMessage(NewCoords.X.ToString + ' : ' + NewCoords.Y.ToString);
NewCoords.Free;

// OK - WORKING
NewCoords := TCoords.Create(Point(12, 120));
ShowMessage(NewCoords.X.ToString + ' : ' + NewCoords.Y.ToString);
NewCoords.Free;

// NOT WORKING as expected
NewCoords := TCoords.Create(12, 120);
ShowMessage(NewCoords.X.ToString + ' : ' + NewCoords.Y.ToString);
NewCoords.Free;

Блок координат с определением объекта TCoords

unit Coords;

{$mode objfpc}{$H+}

interface

uses
    Classes;

type
    // Flexible X,Y coordinates object.
    TCoords = class(TObject)

    // these declarations are accessible within this unit only
    private
        // this is the variable we are working with
        FCoords: TPoint;
        // property for this function is unnecessary, but I like it as it is
        function IsInitialized: Boolean;

    // these declarations are accessible to all
    public
        // this creates instance of TCoords initialized to PointOutOfReach
        constructor Create; reintroduce;
        // this creates instance of TCoords initialized to user coordinates
        constructor Create(const ACoords: TPoint);

        // THIS ONE DOES NOT WORK, strangely returns Point(0, 0)
        constructor Create(const AX, AY: Integer);

        // this indicates if instance was initialized or not by the user
        property Initialized: Boolean read IsInitialized;
        // this works directly with private FCoords variable storing coordinates
        property P: TPoint read FCoords write FCoords;
        // these two are shortcuts for X,Y coordinates' direct access
        property X: Integer read FCoords.X write FCoords.X;
        property Y: Integer read FCoords.Y write FCoords.Y;

    end;

implementation

var
  // this gets initialized when loading this unit
  PointOutOfReach: TPoint;

constructor TCoords.Create;
begin
    // this is the same as `inherited`, but I like to be explicit
    inherited Create;
    // since called without argument, we have to ensure, there is some nonsense
    FCoords := PointOutOfReach;
end;

constructor TCoords.Create(const ACoords: TPoint);
begin
    // this is the same as `Create`, but I like to be explicit
    TCoords.Create;
    // in the previous mandatory call we have initialized FCoords already
    // but to PointOutOfReach; here we overwrite it with user coordinates
    FCoords := ACoords;
end;

constructor TCoords.Create(const AX, AY: Integer);
begin
    // this is the same as `Create(TPoint)`, but I like to be explicit
//    TCoords.Create(Point(AX, AY));

    // Why can't I call the above, shouldn't it be the very same?
    Create(Point(AX, AY));
end;

function TCoords.IsInitialized: Boolean;
begin
    // this returns True in case FCoords has been initialized
    // initialized means here for the FCoords point to be different from PointOutOfReach
    // achieved either by calling `Create(APoint)`, or later overwriting PointOutOfReach
    Result := FCoords <> PointOutOfReach;
end;

initialization

    // initialize PointOutOfReach to "impossible" coordinates when loading unit
    PointOutOfReach := Point(MAXINT, MAXINT);

end.

Заранее спасибо, похоже, я сам не вижу разницы между этими двумя.


Исправлена ​​основная ошибка - без изменений

Исправлено отсутствие overload; в объявлениях конструкторов. К сожалению, получаю 0,0 координат от последнего конструктора.

Ответы [ 2 ]

2 голосов
/ 18 мая 2019

Дэвид Хеффернан изложил причину моего подхода, чтобы не работать в комментарии, позвольте мне процитировать:

TCoords.Create(Point(AX, AY)) создает новый экземпляр. Когда вы делаете это внутри конструктора, у вас есть два экземпляра. Замените его на Create(Point(AX, AY)).

Спасибо за разъяснения!


Если даже решить, я думаю, что лучший подход будет не цепочки этих constructor s .


Применение этого правила работает, рабочий фрагмент с освобожденными конструкторами:

constructor TCoords.Create;
begin
    inherited Create;
    // since called without argument, we have to ensure,
    // there are some corner-case coordinates, so that we can
    // differentiate between a [0:0] and uninitialized state
    FCoords := PointOutOfReach;
end;

constructor TCoords.Create(const ACoords: TPoint);
begin
    inherited Create;
    FCoords := ACoords;
end;

constructor TCoords.Create(const AX, AY: Integer);
begin
    inherited Create;
    FCoords := Point(AX, AY);
end;

PS: чтобы код работал, пока я не вижу необходимости применять «сеттеры» как в Насреддине Галфуте ответ .

0 голосов
/ 18 мая 2019

Это способ, которым вы объявляете свойства X и Y, изменяете их на

property X: Integer read FCoords.X write SetCoordX;
property Y: Integer read FCoords.Y write SetCoordY;

procedure TCoords.SetCoordX(const Value: Integer);
begin
  FCoords.X := Value;
end;

procedure TCoords.SetCoordY(const Value: Integer);
begin
  FCoords.Y := Value;
end;

Это связано с тем, как Delphi назначает свойства.В вашем случае вы присваиваете неявную переменную, добавляемую компилятором при получении значения X.

Кажется, я не могу вспомнить, где я об этом читал, я найду ее и отредактирую свой ответ

...