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 ответов
я вижу две причины, по которым ваш исходный набор объявлений не должен компилироваться чисто:
-
должно быть предупреждение на
TCellPhone
что его конструктор скрывает метод базового класса. Это связано с тем, что метод базового класса виртуальный, и компилятор беспокоится, что вы вводите новая метод с тем же именем без переопределения метода базового класса. Не имеет значения, что подписи различаются. Если ваше намерение действительно скрыть метод базового класса, то вам нужно использоватьreintroduce
о декларации потомков, как показала одна из ваших слепых догадок. Единственная цель этой директивы-подавить предупреждение; она не влияет на поведение во время выполнения.игнорирование того, что произойдет с
TIPhone
позже, послеTCellPhone
объявление, что вы хотите. Он скрывает метод предка, но вы также хотите, чтобы он был виртуальным. Он не унаследует виртуальность метода предка, потому что это два совершенно разных метода, которые просто имеют одно и то же имя. Поэтому вам нужно использоватьvirtual
о новой декларации, а также.TCellPhone = class(TComputer) public constructor Create(Cup: Integer; Teapot: string); reintroduce; virtual; end;
конструктор базового класса,
TComputer.Create
, также скрывает метод его предка,TObject.Create
, но так как способ вTObject
не виртуальный, компилятор не предупреждает об этом. Скрытие не виртуальных методов происходит все время и обычно непримечательный. -
вы должны получить на
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;
Не забывайте использовать одно и то же имя для двух разных конструкторов