Delphi: понимание конструкторов

я хочу понять

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

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

учитывая гипотетический набор объекты:

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 переопределяет оба конструктора, вызывая унаследованную версию каждого

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

я хочу понять почему. Исправить это было бы очевидно.

посмотреть также

  • Delphi: как скрыть конструкторы предков?
  • восстановление функций в Delphi
  • Delphi: как добавить другой конструктор к потомку?

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

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

Edit 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 ключевое слово (я знаю, я не хочу!--17--> потому что это скроет другой конструктор, который я не хочу):

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-й create говорит, что он должен быть отмечен перегрузкой, что я и делаю (на самом деле я отмечу оба как перегрузку, так как я знаю, что произойдет, если я этого не сделаю):

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

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

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

4 ответов


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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

требуемый приказывать детализирован в документация:

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

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

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


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

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

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

  • первый конструктор перегружается один из двух конструкторов, унаследованных и переопределяющих его брата. Для этого используйте overload; override перегрузить TCellPhone при переопределении другой конструктор.
  • это делается, второй конструктор нуждается в простой override чтобы переопределить его брата.

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

constructor Create; Overload;

constructor Values; Overload;

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