Delphi: Понимание конструкторов - PullRequest
34 голосов
/ 06 октября 2010

я ищу понять

  • виртуальный
  • переопределить
  • перегрузить
  • повторно ввести

при применении к конструкторам объектов.Каждый раз, когда я случайным образом добавляю ключевые слова до тех пор, пока компилятор не отключится - и (после 12 лет разработки с Delphi) я предпочел бы знать, что я делаю, а не пытаться делать что-то случайным образом.objects:

TComputer = class(TObject)
public
    constructor Create(Cup: Integer); virtual;
end;

TCellPhone = class(TComputer)
public
    constructor Create(Cup: Integer; Teapot: string); virtual;
end;

TiPhone = class(TCellPhone)
public
    constructor Create(Cup: Integer); override;
    constructor Create(Cup: Integer; Teapot: string); override;
end;

То, как я хочу, чтобы они вели себя, вероятно, очевидно из объявлений, но:

  • TComputer имеет простой конструктор, и потомки могут его переопределить
  • TCellPhone имеет альтернативный конструктор, и потомки могут переопределить его
  • TiPhone переопределяет оба конструктора, вызывая унаследованную версию каждого

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

Я хочу понять, почему 1038 *.Исправление было бы тогда очевидным.

См. Также

Редактировать: Я также ищучтобы получить некоторые рассуждения порядка virtual, override, overload, reintroduce.Потому что при попытке всех комбинаций ключевых слов количество комбинаций взрывается:

  • virtual;перегрузка;
  • виртуальная;переопределить;
  • переопределить;перегрузка;
  • переопределение;виртуальный;
  • виртуальный;переопределить;перегрузка;
  • виртуальная;перегрузки;переопределение;
  • перегрузка;виртуальный;переопределить;
  • переопределить;виртуальный;перегрузка;
  • переопределение;перегрузки;виртуальный;
  • перегрузка;переопределить;виртуальный;
  • и т. д.

Редактировать 2: Полагаю, нам следует начать с " Дана ли даже иерархия объектов? " Еслинет, почему бы и нет?Например, является ли принципиально неверным иметь конструктор от предка?

TComputer = class(TObject)
public
    constructor Create(Cup: Integer); virtual;
end;

TCellPhone = class(TComputer)
public
    constructor Create(Cup: Integer; Teapot: string); virtual;
end;

Я бы ожидал, что TCellPhone теперь имеет два конструктора.Но я не могу найти комбинацию ключевых слов в Delphi, чтобы заставить ее думать, что это правильно.Неужели я в корне ошибочен, думая, что здесь, в TCellPhone?


, у меня может быть два конструктора.но это помогает объяснить мое мышление.Возможно, вы можете увидеть, основываясь на моих мыслительных процессах, какую фундаментальную часть я упускаю, чтобы прояснить все.

Теперь эти объявления не компилируются:

//Method Create hides virtual method of base type TComputer:
TCellPhone = class(TComputer)
   constructor Create(Cup: Integer; Teapot: string);  virtual;

//Method Create hides virtual method of base type TCellPhone:
TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer); override;
   constructor Create(Cup: Integer; Teapot: string); overload;  <--------
end;

Итак, сначала я попробую починить TCellPhone.я начну со случайного добавления ключевого слова overload (я знаю, что не хочу reintroduce, потому что это скрыло бы другой конструктор, который мне не нужен):

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer; Teapot: string); virtual; overload;
end;

Но этотерпит неудачу: Field definition not allowed after methods or properties.

из опыта я знаю, что, даже если у меня нет поля после метода или свойства, если я переверну порядок ключевых слов virtual и overload: Delphiзакроет:

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer; Teapot: string); overload; virtual; 
end;

Но я все еще получаю ошибку:

Метод 'Create' скрывает виртуальный метод базового типа 'TComputer'

Поэтому я пытаюсь удалить оба ключевых слова:

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer; Teapot: string);
end;

Но я все еще получаю ошибку:

Метод 'Create' скрывает виртуальный метод базового типа 'TComputer'

Так что я смиряюсь с попыткой reintroduce:

TCellPhone = class(TComputer)
public
   constructor Create(Cup: Integer; Teapot: string); reintroduce;
end;

И теперь TCellPhone компилируется, но для TiPhone все стало намного хуже:

TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer); override; <-----cannot override a static method
   constructor Create(Cup: Integer; Teapot: string); override; <-----cannot override a static method
end;

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

TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer);
   constructor Create(Cup: Integer; Teapot: string);
end;

Но теперь 2-е создание говорит, что оно должно быть помечено перегрузкой, что я и делаю (на самом деле я помечу оба как перегрузку, так как я знаю, что произойдет, если я не буду):

TiPhone = class(TCellPhone)
public
   constructor Create(Cup: Integer); overload;
   constructor Create(Cup: Integer; Teapot: string); overload;
end;

Все хорошо в разделе interface.К сожалению, мои реализации не будут работать.Мой однопараметрический конструктор TiPhone не может вызвать унаследованный конструктор:

constructor TiPhone.Create(Cup: Integer);
begin
    inherited Create(Cup); <---- Not enough actual parameters
end;

Ответы [ 4 ]

16 голосов
/ 07 октября 2010

Я вижу две причины, по которым ваш первоначальный набор объявлений не должен компилироваться без ошибок:

  1. Должно быть предупреждение в TCellPhone, что его конструктор скрывает метод базового класса.Это связано с тем, что метод базового класса virtual , и компилятор беспокоится о том, что вы вводите метод new с тем же именем без переопределения метода базового класса.Неважно, что подписи отличаются.Если вы действительно хотите скрыть метод базового класса, то вам нужно использовать reintroduce в объявлении потомка, как показала одна из ваших догадок.Единственная цель этой директивы - подавить предупреждение;это не влияет на поведение во время выполнения.

    Игнорирование того, что произойдет с TIPhone позже, следующее объявление TCellPhone - то, что вам нужно.Он скрывает метод предка, но вы также хотите, чтобы он был виртуальным.Он не унаследует виртуальность метода предка, потому что это два совершенно разных метода, которые просто имеют одно и то же имя.Поэтому вам также необходимо использовать virtual в новом объявлении.

    TCellPhone = class(TComputer)
    public
      constructor Create(Cup: Integer; Teapot: string); reintroduce; virtual;
    end;
    

    Конструктор базового класса, TComputer.Create, также скрывает метод своего предка,TObject.Create, но поскольку метод в TObject не является виртуальным, компилятор не предупреждает об этом.Скрытие не виртуальных методов происходит постоянно и, как правило, ничем не примечательно.

  2. Вы должны получить error в TIPhone, потому что больше нет ни одного аргументаконструктор для переопределения.Вы спрятали это в TCellPhone.Поскольку вы хотите иметь два конструктора, reintroduce ясно не был правильным выбором для использования ранее.Вы не хотите скрывать конструктор базового класса;вы хотите дополнить его другим конструктором.

    Поскольку вы хотите, чтобы оба конструктора имели одно и то же имя, вам необходимо использовать директиву overload.Эту директиву необходимо использовать для всех исходных объявлений - при первом введении каждой отдельной подписи последующие объявления в потомках.Я думал, что это требуется для всех объявлений (даже базового класса), и это не помешает, но, думаю, это не обязательно.Итак, ваши объявления должны выглядеть следующим образом:

    TComputer = class(TObject)
    public
      constructor Create(Cup: Integer);
        overload; // Allow descendants to add more constructors named Create.
        virtual;  // Allow descendants to re-implement this constructor.
    end;
    
    TCellPhone = class(TComputer)
    public
      constructor Create(Cup: Integer; Teapot: string);
        overload; // Add another method named Create.
        virtual;  // Allow descendants to re-implement this constructor.
    end;
    
    TiPhone = class(TCellPhone)
    public
      constructor Create(Cup: Integer);
        override; // Re-implement the ancestor's Create(Integer).
      constructor Create(Cup: Integer; Teapot: string);
        override; // Re-implement the ancestor's Create(Integer, string).
    end;
    

Современная документация говорит, в каком порядке все должно идти:

* 1057 вновь *;* * +1058 перегрузки ; связывание ; соглашение о вызовах ; аннотация ; предупреждение

где привязка равна виртуальная , динамическая или переопределение ; соглашение о вызовах - регистр , паскаль , cdecl , stdcall или safecall предупреждение является платформа , устарела или библиотека .

Это шесть различных категорий, нопо моему опыту, в любой декларации редко бывает больше трех.(Например, функции, для которых необходимо указать соглашения о вызовах, вероятно, не являются методами, поэтому они не могут быть виртуальными.) Я никогда не помню порядок;Я никогда не видел это до сегодняшнего дня.Вместо этого я думаю, что более полезно вспомнить цель каждой директивы .Когда вы помните, какие директивы вам нужны для различных задач, у вас останется всего две или три, и тогда довольно просто экспериментировать, чтобы получить действительный порядок.Компилятор может принимать несколько заказов, но не волнуйтесь - порядок не важен для определения значения.Любой порядок, принимаемый компилятором, будет иметь то же значение, что и любой другой (за исключением соглашений о вызовах; если упомянуто более одного из них, учитывается только последний, так что не делайте этого).

Итак, тогда вам просто нужно запомнить цель каждой директивы и подумать, какие из них не имеют никакого смысла вместе.Например, вы не можете использовать reintroduce и override одновременно, потому что они имеют противоположные значения.И вы не можете использовать virtual и override вместе, потому что одно подразумевает другое.

Если у вас накапливается много директив, вы всегда можете вырезать overload из картинки, пока вы работаетеостальные директивы вам нужны.Дайте вашим методам разные имена, определите, какие из других директив им нужны сами, а затем добавьте overload обратно, пока вы снова дадите им те же имена.

5 голосов
/ 06 октября 2010

Обратите внимание, что у меня нет Delphi 5, поэтому я основываю свои ответы на новейшей версии Delphi XE. Я не думаю, что это действительно будет иметь значение, но если это произойдет, вы были предупреждены. :)

Это в основном основано на http://docwiki.embarcadero.com/RADStudio/en/Methods,, который является текущей документацией о том, как работают методы. Ваш файл справки Delphi 5, вероятно, также имеет нечто подобное.

Во-первых, виртуальный конструктор может не иметь особого смысла. Есть несколько случаев, когда вы этого хотите, но это, вероятно, не один. Взгляните на http://docwiki.embarcadero.com/RADStudio/en/Class_References для ситуации, когда вам нужен виртуальный конструктор - если вы всегда знаете тип ваших объектов при кодировании, тем не менее.

Проблема, с которой вы сталкиваетесь в конструкторе с одним параметром, заключается в том, что ваш родительский класс не имеет самого конструктора с одним параметром - унаследованные конструкторы не предоставляются. Вы не можете использовать inherited для перехода на несколько уровней в иерархии, вы можете позвонить только своему непосредственному родителю. Вам нужно будет вызвать конструктор с 2 параметрами с некоторым значением по умолчанию или добавить конструктор с 1 параметром в TCellPhone.

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

  • virtual - Отметьте это как функцию, для которой вы хотите диспетчеризацию во время выполнения (допускает полиморфное поведение). Это только для начального определения, а не при переопределении в подклассах.
  • override - Предоставить новую реализацию для виртуального метода.
  • overload - Отметить функцию с тем же именем, что и у другой функции, но с другим списком параметров.
  • reintroduce - Сообщите компилятору, что вы на самом деле намеревались скрыть виртуальный метод, вместо того, чтобы просто забыть указать override.

Требуемый порядок подробно описан в документации:

Объявления методов могут включать специальные директивы, которые не используются с другими функциями или процедурами. Директивы должны появиться в классе только декларация, а не в определении декларация, и всегда должна быть перечислены в следующем порядке:

реинтродукции; перегрузки; связывание; соглашение о вызовах; Аннотация; предупреждение

где привязка виртуальная, динамическая или переопределить; Соглашение о вызовах зарегистрироваться, паскаль, cdecl, stdcall или директива SafeCall; и предупреждение является платформой, устарела или библиотека.

2 голосов
/ 07 октября 2010

Это рабочая реализация требуемых определений:

program OnConstructors;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type

TComputer = class(TObject)
public
    constructor Create(Cup: Integer); virtual;
end;

TCellPhone = class(TComputer)
public
    constructor Create(Cup: Integer; Teapot: string); reintroduce; overload; virtual;
end;

TiPhone = class(TCellPhone)
public
    constructor Create(Cup: Integer); overload; override;
    constructor Create(Cup: Integer; Teapot: string); override;
end;

{ TComputer }

constructor TComputer.Create(Cup: Integer);
begin
  Writeln('Computer: cup = ', Cup);
end;

{ TCellPhone }

constructor TCellPhone.Create(Cup: Integer; Teapot: string);
begin
  inherited Create(Cup);
  Writeln('Cellphone: teapot = ', Teapot);
end;

{ TiPhone }

constructor TiPhone.Create(Cup: Integer);
begin
  inherited Create(Cup);
  Writeln('iPhone: cup = ', Cup);
end;

constructor TiPhone.Create(Cup: Integer; Teapot: string);
begin
  inherited;
  Writeln('iPhone: teapot = ', Teapot);
end;

var
  C: TComputer;

begin

  C := TComputer.Create(1);
  Writeln; FreeAndNil(C);

  C := TCellPhone.Create(2);
  Writeln; FreeAndNil(C);
  C := TCellPhone.Create(3, 'kettle');
  Writeln; FreeAndNil(C);

  C := TiPhone.Create(4);
  Writeln; FreeAndNil(C);
  C := TiPhone.Create(5, 'iPot');

  Readln; FreeAndNil(C);

  end.

с результатами:

Computer: cup = 1

Computer: cup = 2

Computer: cup = 3
Cellphone: teapot = kettle

Computer: cup = 4
iPhone: cup = 4

Computer: cup = 5
Cellphone: teapot = iPot
iPhone: teapot = iPot

Первая часть соответствует this .Определение двух конструкторов TiPhone затем выполняется следующим образом:

  • Первый конструктор перегружает один двух унаследованных конструкторов и переопределяет его одноуровневый элемент.Чтобы достичь этого, используйте overload; override, чтобы перегрузить один TCellPhone, переопределяя другой конструктор.
  • Для этого второму конструктору необходим простой override, чтобы переопределить своего родного брата.*
0 голосов
/ 19 августа 2016

использовать перегрузку на обоих, это то, как я это делаю, и это работает.

constructor Create; Overload;<- используйте здесь перегрузку </p>

constructor Values; Overload; <- и здесь </p>

не забудьте использовать одно и то же имя для двух разных конструкторов

...