Inherited — Ключевое слово Delphi


Содержание

Inherited — Ключевое слово Delphi

Давайте, например, построим два класса-наследника нашего класса TPerson. Они будут конкретизировать сведения об абстрактной личности, которую представляет класс TPerson. Один класс-наследник TStudent пусть представляет студента, а другой класс TEmpl представляет сотрудника некоторой организации. Объявления этих классов могут выглядеть так:

Эти классы наследуют все свойства и методы своего базового класса TPerson. В обоих классах объявлены новые открытые методы PersonToStr. Предполагается, что их назначение — представить информацию о человеке в виде единой строки. Такая строка полезна во многих случаях. В частности, например, для подготовки шапки характеристики, как в приложении, которое вы разрабатывали в разд. 1.3.
Реализация этих методов может иметь вид:

Комментарии к этому коду, наверное, излишни. Имеет смысл сказать только о смысле свойств Depl и Dep2 в новых классах. Как видно из кода, свойство Depl в классе TStudent указывает студенческую группу, а в классе TEmpl — отдел. Свойство Dep2 указывает организацию (институт), в которой работает (учится) человек. Вы можете, конечно, воспринимать свойства методов иначе и соответственно переделать приведенные функции.

Можете добавить в ваше тестовое приложение еще одно окно Edit для задания свойства Dep2. Но главное сейчас — посмотреть соотношение родительского и производных классов.
Можете оставить прежнее объявление
var Pers: TPerson; но при создании объекта указать какой-то производный класс, например:

Все будет работать, т.е. производный класс присвоится переменной родительского класса. Но если вы попробуете записать оператор вида:
ShowMessage(Pers.PersonToStr);
то получите сообщение об ошибке:

«Undeclared identifier: ‘PersonToStr’»,

поскольку в классе TPerson не объявлен метод PersonToStr. Но все будет нормально, если вы измените этот оператор следующим образом:

ShowMessage((Pers as TStudent).PersonToStr);

В этом операторе вы используете операцию as, определенную для классов. Ее первым операндом является объект, вторым — класс. Если А — объект, а С -класс, то выражение A as С возвращает тот же самый объект, но рассматриваемый как объект класса С (В смысле: у объекта А будут свойства и методы объекта С). Операция даст результат, если указанный класс С является классом объекта А или одним из наследников этого класса. В нашем случае это условие соблюдается, так что операция as возвращает объект Pers, рассматриваемый как объект TStudent. А в таком объекте метод PersonToStr имеется. Если бы вы сразу объявили переменную Pers класса TStudent:

var Pers: TStudent;

то операция as не потребовалась бы. Выражение Pers.PersonToStr было бы воспринято нормально.

Для классов определена еще одна операция — is. Выражение A is С позволяет определить, относится ли объект А к классу С или к одному из его потомков. Если относится, то операция is возвращает true, в противном случае — false. Например, в нашем примере при любом объявлении класса создаваемого объекта Pers выражение Pers is TPerson вернет true. Но выражение Pers is TStudent вернет true, если объект создан как объект TStudent, и вернет false, если объект создан как объект TPerson.

Теперь посмотрим наследование методов. Давайте введем метод PersonToStr не только в производные, но и в базовый класс TPerson. Для этого класса его реализация может быть очень простой:

function TPerson.PersonToStr: string; begin Result := FName; end;

Теперь метод PersonToStr объявлен с одним и тем же именем и в родительском, и в производных классах. Иначе говоря, вы переопределили или перегрузили родительский метод в производных классах. Теперь вы сможете увидеть, что выражение Pers.PersonToStr будет всегда нормально срабатывать.

Но вызываться будет метод того класса, который указан в объявлении переменной Pers. Если она объявлена как переменная класса TPerson, то всегда будет вызываться метод этого класса, даже если вы занесли в эту переменную указатель на объект класса TStudent или TEmpl. Если вы объявите переменную Pers класса TStudent, и занесете в нее указатель на объект класса TStudent, то вызываться будет метод этого класса. Если вы все-таки хотите в этом случае вызвать метод родительского класса, надо применить к переменной Pers явное приведение типа:

При реализации метода, переопределенного любым способом в классе-наследнике, можно вызывать метод класса-родителя. Для этого перед именем метода при его вызове записывается ключевое слово inherited. Например, оператор

вызывает метод PersonToStr родительского класса.

Если записать слово inherited и после него не писать имя вызываемого метода, то будет вызываться наследуемый метод, совпадающий по имени с именем того метода, из которого он вызывается. Например, если в переопределяемом конструкторе встречается оператор inherited; то будет вызван конструктор родительского класса. Этим мы уже пользовались при написании конструкторов и деструкторов класса TPerson.

В следуйшем уроке мы окунёмся в самые дебри классов.

Как работает inherited?

The reserved word inherited plays a special role in implementing polymorphic behavior. It can occur in method definitions, with or without an identifier after it.If inherited is followed by the name of a member, it represents a normal method call or reference to a property or field, except that the search for the referenced member begins with the immediate ancestor of the enclosing method’s class. For example, when:

occurs frequently in the implementation of constructors. It calls the inherited constructor with the same parameters that were passed to the descendant.

Заметки о Pascal, Delphi и Lazarus

Следует ожидать переводов разделов справочной системы Delphi, компиляций из учебников, переводы статей, «путевые заметки» и прочие интересности. Блог прежде всего ориентирован на студентов, но опытных людей я тоже буду рад видеть;-)

среда, 2 мая 2012 г.

Методы

Метод – это процедура или функция, связанная с классом. При вызове метода определяется объект (или, если это метод класса, то класс) с которым должен работать метод. Например, SomeObject.Free вызывает метод Free объекта SomeObject.

О методах

Внутри объявления класса методы представлены заголовками процедур или функций, которые работают как упреждающие объявления. Где-то после объявления класса (но в этом же модуле) каждый метод должен быть реализован добавлением определяющего объявления. Например, предположим, что объявление TMyClass включает метод с именем DoSomething:

Определяющее объявление для DoSomething должно быть включено ниже в этом же модуле:

Класс может быть объявлен в модуле как в секции interface, так и в секции implementation, но определяющие объявления методов должны быть расположены в секции implementation.

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


Объявления методов могут включать специальные директивы, которые не используются при объявлении прочих процедур и функций. Директивы должны указываться только в объявлении класса, но не в определяющем объявлении, и могут следовать только в следующем порядке: reintroduce; overload; binding; calling convention; abstract; warning, где:

  • binding может принимать значения: virtual, dynamic или override;
  • calling convention может принимать значения: register, pascal, cdecl, stdcall или safecall;
  • warning может принимать значения: platform, deprecated или library.

Ключевое слово Inherited

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

Если за ключевым словом inherited следует имя компонента, оно обозначает обычный вызов метода, обращение к свойству или полю, за исключением того, что поиск компонента, к которому идет обращение, начинается с непосредственного предка класса, к которому относится компонент. Например, если в определяющем объявлении метода присутствует:

происходит вызов наследуемого конструктора Create.

Когда за ключевым словом inherited не следует идентификатора, обращение идет к методу с таким же именем, как вызывающий метод, или (в том случае, когда вызывающий метод является обработчиком сообщений) к наследуемому обработчику для этого же сообщения. В этом случае вызов наследуемого метода не принимает явных параметров, но при этом выполняется неявная передача параметров из вызывающего метода. Например, вызов:

встречается часто в реализации конструкторов. Вызывает наследуемый конструктор с таким же списком параметров (передаваемых наследуемому конструктору).

Идентификатор Self

При реализации метода идентификатор Self обращается к объекту, из которого происходит вызов метода. Для примера приведем реализацию метода Add класса TCollection, объявленного в модуле Classes:

Метод Add вызывает метод Create класса, на который ссылается поле FitemClass (который всегда является наследником TCollectionItem). TCollectionItem.Create принимает единственный параметр типа TСollection. Таким образом, метод Add передает экземпляр объекта типа TCollection, вызывающего его. Это аналогично коду:

Self полезен по самым различным причинам. Например, если идентификатор компонента объявлен в классе, он может быть объявлен повторно в блоке метода, относящегося к этому классу. В этом случае, вы можете получить доступ к идентификатору компонента, используя вызов Self.Identifier.

Связывание методов

Связывание методов может быть статическим (static) (по умолчанию), виртуальным (virtual) или динамическим (dynamic). Виртуальные и динамические методы могут перекрываться и они могут быть абстрактными. Это подразделение методов начинает иметь значение, когда переменная одного типа класса хранит значение типа класса-потомка. Оно определяет, какая реализация метода будет активирована при запуске метода.

Статические методы

Методы по умолчанию являются статическими. При вызове статического метода, объявленный при компиляции тип класса или объектная переменная, использованные при вызове метода определяют, какая из реализаций метода активируется. В следующем примере метод Draw является статическим:

При таком объявлении приведенный ниже код иллюстрирует вызов статического метода. Во втором вызове Figure.Draw, переменная Figure ссылается на класс TRectangle, но вызов метода активирует реализацию метода Draw в классе TFigure, поскольку объявленный тип переменной Figure – это TFigure:

Виртуальные и динамические методы

Для того, чтобы сделать метод виртуальным или динамическим необходимо включить в его объявление соответствующую директиву (virtual или dynamic). Виртуальные и динамические методы, в отличии от статических могут быть перекрыты в классе-предке. При вызове перекрытого метода, решение о том, какую реализацию метода активировать, принимает действительный (определенный в процессе работы программы) тип класса или объекта, использванный при вызове метода.

Чтобы перекрыть метод его следует объявить повторно с указанием директивы override. Перекрывающее объявление должно совпадать с объявлением в классе-предке по порядку и типу параметров, а так же типу возвращаемого результата (для функций). В следующем примере метод Draw, объявленный в классе TFigure перекрывается в двух классах предках:

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

Только виртуальные и динамические методы могут быть перекрыты. Тем не менее, любые методы могут быть перегружены. Компилятор Delphi так же поддерживает концепцию финального виртуального метода. Если при объявлении виртуального метода было использовано ключевое слово final, классы-предки не смогут перекрывать его. Применение ключевого слова final может быть хорошим инженерным решением, которое может помочь документировать планируемое применение класса. Кроме того, оно помогает компилятору оптимизировать код.

Виртуальный или динамический?

В Delphi для Win32, виртуальные и динамические методы семантически одинаковы. Тем не менее, они отличаются в реализации обработки вызовов методов при выполнении программы. Виртуальные методы оптимизированы с точки зрения скорости, а динамические — снижают размер результирующего кода.

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

Замечание: Динамические методы следует использовать только в тех случаях, когда выгода от их применения очевидна. Во всех обычных ситуациях следует использовать виртуальные методы.

Скрывать или перекрывать?

Если объявление метода определяет такой же идентификатор и набор параметров, какой уже присутствует в объявлении метода в классе-предке, но при этом не использует директиву override, новое объявление просто скрывает наследуемый метод, не перекрывая его. Оба метода будут существовать в классе-предке, в котором имя метода связано статически. Например:

Директива Reintroduce

Директива reintroduce подавляет предупреждения компилятора о сокрытии объявленных ранее виртуальных методов. Например:

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

Илон Маск рекомендует:  Стиль ссылки при наведении
Абстрактные методы

Абстрактные методы – это виртуальные или динамические методы, не имеющие реализации в том классе, где они объявлены. Они должны быть реализованы в классе-потомке. Абстрактные методы должны объявляться с использованием директивы abstract, следующей за директивой virtual или dynamic. Например:


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

Методы класса

Большая часть методов называются методами экземпляров, поскольку они работают с отдельными экземплярами объекта. Метод класса – это метод (отличный от конструктора), который оперирует классами, но не объектами. Существует два типа методов класса: обычные и статические.

Обычные методы класса

Объявление метода класса должно начинаться с зарезервированного слова class. Например:

Определяющее объявление метода класса также начинается с зарезервированного слова class:

В определяющем объявлении метода класса идентификатор Self представляет класс, в котором вызывается метод (может быть классом-потомком, по отношению к классу, в котором объявлен метод). Если методы вызывается в классе С, Self имеет тип class of C.

Таким образом, вы не сможете использовать Self для доступа к полям, свойствам или обычным методам экземпляра. Вы можете использовать Self для вызова конструкторов, прочих методов класса или для доступа к свойствам или полям класса. Метод класса может быть вызван при помощи ссылки на класс или объект. При вызове через ссылку на объект, класс объекта принимает значение Self.

Статические методы класса

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

Для объявления статического метода класса следует добавить ключевое слово класса к объявлению:

Как и обычные методы класса, статические методы могут вызываться при помощи типа класса (без ссылки на экземпляр класса):

Перегрузка методов

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

Если вы перегружаете виртуальный метод, — пользуйтесь директивой reintroduce:

Внутри класса вы не можете установить видимость перегружаемых методов с одинаковыми именами в published. Обработка информации о типах в режиме выполнения программы требует уникального имени для компонентов с такой спецификацией видимости.

Методы, служащие для спецификации доступа к значению свойств (read или write) не могут быть перегружены. Реализация перегружаемых методов должна повторять список параметров, содержащихся в объявлении класса.

Конструкторы

Конструктор – это особый метод, который создает и инициализирует объекты. Объявление конструктора выглядит как объявление обычной процедуры, но начинается с ключевого слова constructor. Примеры:

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

Класс, может иметь более, чем один конструктор, но большая часть классов имеет только один. Обычно для конструкторов используется имя Create.

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

Эта инструкция выделяет память для нового объекта, устанавливает значения для всех целочисленных полей в 0, присваивает значение nil для всех полей указательного типа и устанавливает пустое значение для всех строковых полей. Прочие операции, указанные в реализации конструктора, выполняются позже. Обычно объекты инициализируются значениями, которые передаются как параметры в конструктор. Наконец, конструктор возвращает ссылку на только что созданный и инициализированный объект. Тип возвращаемого значения совпадает с типом, указанным при вызове конструктора.

Если при выполнении конструктора, вызванного инструкцией с указанием ссылки на класс, возникает исключение деструктор Destroy вызывается автоматически для разрушения незаконченного объекта.

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

Далее приведен пример класса и его конструктора:

Первой инструкцией в конструкторе обычно бывает вызов наследуемого конструктора для инициализации унаследованных полей. Затем конструктор инициализирует поля, объявленные в классе-потомке. Поскольку конструктор всегда очищает память, которую он выделяет для создания объекта, все поля порядковых типов принимают значение 0, указатели и переменные типа класс – nil, строковые типы инициализируются пустым значением, а вариантные типы – значением Unassigned. Таким образом нет необходимости в теле конструктора инициализировать поля значениями, отличными от ноля или пустых значений.

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

Деструкторы

Деструктор – это особый метод, который при вызове разрушает объект и освобождает занимаемую им память. Объявление деструктора выглядит как объявление обычной процедуры, но начинается с ключевого слова destructor. Например:

Деструкторы на платформе Win32 используют по умолчанию конвенцию вызова register. Хоть класс и может иметь несколько деструкторов, рекомендуется перекрывать наследуемый метод Destroy, без объявления прочих деструкторов.

Для вызова деструктора вы должны специфицировать экземпляр объекта:

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


Далее приведен пример реализации деструктора:

Последней инструкцией в теле деструктора обычно бывает вызов наследуемого деструктора, которые уничтожает наследуемые поля. При возникновении исключительной ситуации в процессе создании объекта, для разрушения незаконченного объекта автоматически вызывается деструктор Destroy. Это обозначает, что деструктор Destroy должен быть подготовлен к разрушению частично созданных объектов. Поскольку конструктор устанавливает нулевые и пустые значения полей перед выполнением каких-либо других действий, поля типа класс и указатели в частично созданном объекте всегда имеют значение nil. Прежде чем работать с такими полями деструктор должен проверять их на наличие этого значения. Вызов метода Free (объявлен в TObject) – это более подходящий способ для разрушения объекта с предварительной проверкой значений его полей.

Конструкторы классов

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

Следующий пример показывает обычную инициализацию полей класса:

Этот способ плох тем, что приложение включает модуль, объявляющий TBox, даже в том случае, если этот модуль на самом деле не используется им. В приведенном примере класс TBox включается в результирующий бинарный файл, поскольку он упоминается в секции инициализации. Чтобы исправить ситуацию, рассмотрим применение конструкторов класса:

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

Замечание: Несмотря на то, что компилятор уделяет внимание порядку инициализации классов, в некоторых сложных ситуациях он может оказаться случайным. Это происходит, когда конструктор класса зависит от состояния другого класса, который, в свою очередь, зависит от первого.
Замечание: конструктор класса-дженерика или записи может быть выполнен несколько раз. Точное количество запусков конструктора зависит от количества специализированных версий типа-дженерика. Например, конструктор класса для специализированного класса TList&ltString&gt может быть выполнен несколько раз в одном и том же приложении.

Деструкторы классов

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

Замечание: деструктор класса-дженерика или записи может быть выполнен несколько раз. Точное количество запусков деструктора зависит от количества специализированных версий типа-дженерика. Например, деструктор класса для специализированного класса TList&ltString&gt может быть выполнен несколько раз в одном и том же приложении.

Методы-обработчики сообщений

Обработчики сообщений реализуют реакцию на динамически передаваемые сообщения. Синтаксис метода-обработчика сообщения поддерживается для всех платформ. Приложения VCL применяют обработчики сообщений для реагирования на сообщения Windows.

Обработчик сообщения объявляется при помощи директивы message, за которой следует целочисленная константа с диапазоном значений от 1 до 49151, представляющая собой идентификатор сообщения. Для обработчиков сообщений в элементах управления VCL эта константа может быть идентификатором одного из сообщений Win32, определенных в модуле Messages. Метод-обработчик сообщения должен быть процедурой с единственным параметром, передаваемым по ссылке.

Обработчик сообщения не должен содержать директивы override для переопределения наследуемого метода. На самом деле он даже не должен повторять имени или типа параметра перекрываемого метода. Только идентификатор сообщения определяет, какое сообщение должен обработать метод и является ли он перекрывающим методом.

Реализация методов-обработчиков сообщений

Реализация обработчика может вызывать наследуемый метод-обработчик. Например:

Инструкция inherited выполняет поиск по иерархии класса и подключает первый найденный метод с соответствующим идентификатором сообщения и передает ему запись сообщения. Если классы-предки не реализуют метод для обработки сообщения с данным идентификатором, директива inherited вызывает метод DefaultHandler , объявленный в TObject.

Реализация DefaultHandler в TObject не подразумевает выполнения каких-либо операций. Перекрывая DefaultHandler, класс может реализовать собственную обработку сообщений по умолчанию. Для элементов управления на платформе Win32, метод DefaultHandler вызывает Win32 API DefWindowProc.

Обработка сообщений

Обработчики сообщений редко вызываются явно. Обычно сообщения передаются объекту при помощи метода Dispatch , наследуемого от TObject:

Параметр Message передаваемый методу Dispatch должен быть записью, первым полем которой должно быть поле типа Word, содержащее идентификатор сообщения.

Dispatch выполняет поиск обработчика сообщения в иерархии классов (начиная с класса объекта, в котором был вызван этот метод) и запускает первый найденный обработчик для идентификатора, который был ему передан. Если обработчика для сообщения с данным идентификатором не находится, Dispatch вызывает DefaultHandler.

Inherited — Ключевое слово Delphi

Как можно вызвать inherited не у родителя а у дедушки.
Для примера
Задача такая: есть неплохой визуальный сторонний компонент.
Он наследован от Tcustomgrid,
в нем полностью переопределены MouseDown, KeyDown и т.д.
И ведет он себя не так как хотелось.
Надо чтобы управление было как в Tcustomgrid.
Как можно в MouseDown, KeyDown вернуться к Tcustomgrid?

1) Опять переопределить, поместив туда снова «дедушкин» код
2) закомментировать «отцовское» переопределение

1)
в «дедушкином» коде — вызывается inherited «прадедушки»
если переопределить то inherited будет «родителя»
2)
>закомментировать «отцовское» переопределение
в смысле исходним поправить — не выход, и не всегда возможно!
должен же быть способ вызвать виртуальный метод у нужного класса
типа TcustomGrid(mygrid).mousedown(. ) причем mygrid другого типа
в таблице виртуальных методов есть адресса всех inherited методов, как найти адрес дедушки?

есть метод OnMouseOver, перекрытый у твоего класса. Ты знаешь, что инетерсующий тебя класс есть наследник класса TCustomGrid. Тогда пишешь наследник от интересующего тебя класса, у него перекрываешь метод OnMouseOver и в этом методе пишешь TCustomGrid.OnMouseOver.

>Um (25.06.03 14:19)
>в таблице виртуальных методов есть адресса всех inherited методов, как найти адрес дедушки?

В VMT адрес метода внука лежит на месте адреса метода деда — не знает VMT ничего о дедовых методах. Когда мы зовем inherited, то компилятор осуществляет прямой вызов родительского метода, а не косвенный, хотя метод и виртуальный.

Но и TCustomGrid.OnMouseOver тоже не получится, это не BP. Все это неприятный прокол Delphi, imho.

>uw © (25.06.03 16:27)
>не знает VMT ничего о дедовых методах

Знает, в VMT есть ссылка на VMT родительского класса.

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;


type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
end;

TGrandfather = class
procedure Test; virtual;
end;

TFather = class(TGrandfather)
procedure Test; override;
end;

TSon = class(TFather)
procedure Test; override;
procedure CallGrandTest;
end;

var
Form1: TForm1;

// AMethod — pointer to the method of such type: procedure XXX();
procedure CallMethod(const AObject, AMethod: Pointer); register;
asm
call edx
end;

procedure TGrandfather.Test;
begin
ShowMessage(«Grandfather»);
end;

procedure TFather.Test;
begin
inherited;
ShowMessage(«Father»);
end;

procedure TSon.CallGrandTest;
begin
CallMethod(Self, @TGrandfather.Test);
end;

procedure TSon.Test;
begin
ShowMessage(«Son»);
end;

procedure TForm1.FormCreate(Sender: TObject);
var
A: TSon;
begin
A := TSon.Create;
with A do
try
Test;
CallGrandTest;
finally
FreeAndNil(A);
end;
PostQuitMessage(0);
end;

type
TMouseDownProc = procedure (Button: TMouseButton;
Shift: TShiftState; X, Y: Integer) of object;

THackCustomGr > // метод MouseDown — protected

procedure TYourGrid.MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
var
md: TMouseDownProc;
begin
// вызов непосредственно метода TCustomGrid.MouseDown
// для данного экземпляра

TMethod(md).Code := @THackCustomGrid.MouseDown;
TMethod(md).Data := Self;

md(Button, Shift, X, Y);

>reonid © (25.06.03 17:32)

Илон Маск рекомендует:  Курсор сам по себе (IE)

Твой способ даже покрасивее :)

Да нельзя делать такие вещи. Это нарушает принцип наследование. Пишите наследника дедушки, а не его потомка.

>Gamar © (25.06.03 17:56)
В старом турбопаскале, равно как и в современных Сях,
выполнение метода дедушки — вполне законная операция.
Иногда бывает полезна, хотя злоупотреблять не стоит.

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

>Gamar © (25.06.03 17:56)
>Да нельзя делать такие вещи.

Если бы было нельзя делать такие вещи, то код VMcL © (25.06.03 17:19) и код reonid © (25.06.03 17:32) не работали бы.

>VMcL © (25.06.03 17:19)
>reonid © (25.06.03 17:32)

Спасибо. Для меня было откровением, что синтаксис @TGrandfather.Test возможен. Но адрес все же берется не из VMT, а вычисляется компилятором точно так же, как и при inherited.

Вероятно, их код не стоит считать верхом совершенства.

Согласен с VMcL,reonid это самый простой и естественный способ.
Не согласен с Gamar так как на самом деле любой вызов метода объекта работает аналогично TMethod.

>VMcL
>reonid
Я когда прочитал — от радости на стуле подпрыгнул
SQL.ru — слабаки
delphimaster.ru — рулёзззз

Забыл, VMcL, Reonid, — большое спасибо!
>reonid
То-что доктор прописал — клёво!

Delphi: How to call inherited inherited ancestor on a virtual method?

I’m overriding a virtual method, and I want to call inherited. But I don’t want to call the immediate ancestor, I want to call the one before.

I tried casting my self as the ancestor, and call the method on that, but it led to recursive stack overflow:

i tried adding the inherited keyword, but that doesn’t compile:


4 Answers 4

You can’t in a regular language way, as this would break the object oriented aspects of the language.

You can fiddle around with pointers and clever casts to do this, but before even starting to answer that: is this really what you want?

As others mentioned: your need sounds like a serious «design smell» (which is similar to code smell, but more severe.

Edit:

Going down the pointer fiddling road might save you work in the short term, and cost you weeks of work in the long term.
This makes for some good reading on that: Upstream decisions, downstream costs.

Methods (Delphi)

Contents

A method is a procedure or function associated with a >SomeObject.Free calls the Free method in SomeObject .

This topic covers the following material:

  • Method declarations and implementation
  • Method binding
  • Overloading methods
  • Constructors and destructors
  • Message methods

About Methods

Within a >TMyClass includes a method called DoSomething :

A defining declaration for DoSomething must occur later in the module:

While a class can be declared in either the interface or the implementation section of a unit, defining declarations for a class methods must be in the implementation section.

In the heading of a defining declaration, the method name is always qualified with the name of the class to which it belongs. The heading can repeat the parameter list from the class declaration; if it does, the order, type, and names of the parameters must match exactly, and if the method is a function, the return value must match as well.

Method declarations can include special directives that are not used with other functions or procedures. Directives should appear in the class declaration only, not in the defining declaration, and should always be listed in the following order:

  • binding is virtual, dynamic, or override;
  • calling convention is register, pascal, cdecl, stdcall, or safecall;
  • warning is platform, deprecated, or library. For more information about these warning (hinting) directives, see Hinting Directives.

All Delphi directives are listed in Directives.

Inherited

The reserved word inherited plays a special role in implementing polymorphic behavior. It can occur in method definitions, with or without an identifier after it.

If inherited is followed by the name of a member, it represents a normal method call or reference to a property or field, except that the search for the referenced member begins with the immediate ancestor of the enclosing method’s class. For example, when:

occurs in the definition of a method, it calls the inherited Create .

When inherited has no identifier after it, it refers to the inherited method with the same name as the enclosing method or, if the enclosing method is a message handler, to the inherited message handler for the same message. In this case, inherited takes no explicit parameters, but passes to the inherited method the same parameters with which the enclosing method was called. For example:

occurs frequently in the implementation of constructors. It calls the inherited constructor with the same parameters that were passed to the descendant.

Within the implementation of a method, the >Add method in the Classes unit:

The Add method calls the Create method in the >FItemClass field, which is always a TCollectionItem descendant. TCollectionItem.Create takes a single parameter of type TCollection, so Add passes it the TCollection instance object where Add is called. This is illustrated in the following code:

Self is useful for a variety of reasons. For example, a member >Self.Identifier .

For information about Self in class methods, see «Class Operators» in Class References.

Method Binding

Method bindings can be static (the default), virtual, or dynamic. Virtual and dynamic methods can be overridden, and they can be abstract. These designations come into play when a variable of one class type holds a value of a descendent class type. They determine which implementation is activated when a method is called.

Static Methods


Methods are by default static. When a static method is called, the declared (compile-time) type of the >Draw methods are static:

Given these declarations, the following code illustrates the effect of calling a static method. In the second call to Figure.Draw , the Figure variable references an object of >TRectangle , but the call invokes the implementation of Draw in TFigure , because the declared type of the Figure variable is TFigure :

Virtual and Dynamic Methods

To make a method virtual or dynamic, include the virtual or dynamic directive in its declaration. Virtual and dynamic methods, unlike static methods, can be overridden in descendent classes. When an overridden method is called, the actual (run-time) type of the class or object used in the method call—not the declared type of the variable—determines which implementation to activate.

To override a method, redeclare it with the override directive. An override declaration must match the ancestor declaration in the order and type of its parameters and in its result type (if any).

In the following example, the Draw method declared in TFigure is overridden in two descendent classes:

Given these declarations, the following code illustrates the effect of calling a virtual method through a variable whose actual type varies at run time:

Only virtual and dynamic methods can be overridden. All methods, however, can be overloaded; see Overloading methods.

Final Methods

The Delphi compiler also supports the concept of final virtual and dynamic methods. Declarations of final methods have the form:

Here the virtual|dynamic syntax (two keywords and the | pipe between them) is used to specify that one and only one of the virtual or dynamic keywords should be used. Meaningful is only the virtual or dynamic keyword; the pipe symbol itself should be deleted.

When the keyword final is applied to a virtual or dynamic method, no descendent class can override that method. Use of the final keyword is an important design decision that can help document how the class is intended to be used. It can also give the compiler hints that allow it to optimize the code it produces.

Note: The virtual or dynamic keywords must be written before the final keyword.

Virtual versus Dynamic

In Delphi for Win32, virtual and dynamic methods are semantically equivalent. However, they differ in the implementation of method-call dispatching at run time: virtual methods optimize for speed, while dynamic methods optimize for code size.

In general, virtual methods are the most efficient way to implement polymorphic behavior. Dynamic methods are useful when a base class declares many overridable methods that are inherited by many descendent classes in an application, but only occasionally overridden.

Note: Only use dynamic methods if there is a clear, observable benefit. Generally, use virtual methods.

Overriding versus Hiding

If a method declaration specifies the same method identifier and parameter signature as an inherited method, but does not include override, the new declaration merely hides the inherited one without overriding it. Both methods exist in the descendent class, where the method name is statically bound. For example:

Reintroduce

The reintroduce directive suppresses compiler warnings about hiding previously declared virtual methods. For example:

Use reintroduce when you want to hide an inherited virtual method with a new one.

Abstract Methods

An abstract method is a virtual or dynamic method that has no implementation in the class where it is declared. Its implementation is deferred to a descendent class. Abstract methods must be declared with the directive abstract after virtual or dynamic. For example:

You can call an abstract method only in a class or instance of a class in which the method has been overridden.

Class Methods

Most methods are called instance methods, because they operate on an individual instance of an object. A class method is a method (other than a constructor) that operates on classes instead of objects. There are two types of class methods: ordinary class methods and class static methods.

Ordinary Class Methods

The definition of a class method must begin with the reserved word class. For example:

The defining declaration of a class method must also begin with class. For example:

In the defining declaration of a >C , then Self is of the type >C . Thus you cannot use Self to access instance fields, instance properties, and normal (object) methods. You can use Self to call constructors and other class methods, or to access class properties and class fields.

A class method can be called through a class reference or an object reference. When it is called through an object reference, the class of the object becomes the value of Self.

Class Static Methods


Like class methods, class static methods can be accessed without an object reference. Unlike ordinary class methods, class static methods have no Self parameter at all. They also cannot access any instance members. (They still have access to class fields, class properties, and class methods.) Also unlike class methods, class static methods cannot be declared virtual.

Methods are made class static by appending the word static to their declaration, for example:

Like a class method, you can call a class static method through the class type (for example, without having an object reference), such as:

Overloading Methods

A method can be redeclared using the overload directive. In this case, if the redeclared method has a different parameter signature from its ancestor, it overloads the inherited method without hiding it. Calling the method in a descendent class activates whichever implementation matches the parameters in the call.

If you overload a virtual method, use the reintroduce directive when you redeclare it in descendent classes. For example:

Within a class, you cannot publish multiple overloaded methods with the same name. Maintenance of run time type information requires a unique name for each published member:

Methods that serve as property read or write specifiers cannot be overloaded.

The implementation of an overloaded method must repeat the parameter list from the class declaration. For more information about overloading, see Overloading Procedures and Functions in Procedures and Functions (Delphi).

Constructors

A constructor is a special method that creates and initializes instance objects. The declaration of a constructor looks like a procedure declaration, but it begins with the word constructor. Examples:

Constructors must use the default register calling convention. Although the declaration specifies no return value, a constructor returns a reference to the object it creates or is called in.

To create an object, call the constructor method on a class type. For example:

This allocates storage for the new object, sets the values of all ordinal fields to zero, assigns nil to all pointer and class-type fields, and makes all string fields empty. Other actions specified in the constructor implementation are performed next; typically, objects are initialized based on values passed as parameters to the constructor. Finally, the constructor returns a reference to the newly allocated and initialized object. The type of the returned value is the same as the class type specified in the constructor call.

Илон Маск рекомендует:  Что такое код swf_addcolor

If an exception is raised during the execution of a constructor that was invoked on a >Destroy destructor is automatically called to destroy the unfinished object.

When a constructor is called using an object reference (rather than a class reference), it does not create an object. Instead, the constructor operates on the specified object, executing only the statements in the constructor’s implementation, and then returns a reference to the object. A constructor is typically invoked on an object reference in conjunction with the reserved word inherited to execute an inherited constructor.

Here is an example of a class type and its constructor:

The first action of a constructor is usually to call an inherited constructor to initialize the object’s inherited fields. The constructor then initializes the fields introduced in the descendent class. Because a constructor always clears the storage it allocates for a new object, all fields start with a value of zero (ordinal types), nil (pointer and class types), empty (string types), or Unassigned (variants). Hence there is no need to initialize fields in a constructor’s implementation except to nonzero or nonempty values.

When invoked through a class-type identifier, a constructor declared virtual is equivalent to a static constructor. When combined with class-reference types, however, virtual constructors allow polymorphic construction of objects—that is, construction of objects whose types are not known at compile time. (See Class References.)

Destructors

A destructor is a special method that destroys the object where it is called and deallocates its memory. The declaration of a destructor looks like a procedure declaration, but it begins with the word destructor. Example:

Destructors on Win32 must use the default register calling convention. Although a >Destroy method and declare no other destructors.

To call a destructor, you must reference an instance object. For example:

When a destructor is called, actions specified in the destructor implementation are performed first. Typically, these consist of destroying any embedded objects and freeing resources that were allocated by the object. Then the storage that was allocated for the object is disposed of.

Here is an example of a destructor implementation:

The last action in a destructor implementation is typically to call the inherited destructor to destroy the inherited fields of the object.

When an exception is raised during the creation of an object, Destroy is automatically called to dispose of the unfinished object. This means that Destroy must be prepared to dispose of partially constructed objects. Because a constructor sets the fields of a new object to zero or empty values before performing other actions, >Destroy offers a convenient way to check for nil values before destroying an object.

Class Constructors

A class constructor is a special class method that is not accessible to developers. Calls to class constructors are inserted automatically by the compiler into the initialization section of the unit where the class is defined. Normally, class constructors are used to initialize the static fields of the class or to perform a type of initialization, which is required before the class or any class instance can function properly. Even though the same result can be obtained by placing class initialization code into the initialization section, class constructors have the benefit of helping the compiler decide which classes should be included into the final binary file and which should be removed from it.

The next example shows the usual way of initializing class fields:

This method has a big disadvantage: even though an application can include the unit in which TBox is declared, it may never actually use the TBox class. In the current example, the TBox class is included into the resulting binary, because it is referenced in the initialization section. To alleviate this problem, consider using class constructors:

In this case, the compiler checks whether TBox is actually used anywhere in the application, and if it is used, a call to the class constructor is added automatically to the initialization section of the unit.

Note: Even though the compiler takes care of ordering the initialization of classes, in some complex scenarios, ordering may become random. This happens when the class constructor of a class depends on the state of another class that, in turn, depends on the first class.

Note: The class constructor for a generic class or record may execute multiple times. The exact number of times the class constructor is executed in this case depends on the number of specialized versions of the generic type. For example, the class constructor for a specialized TList class may execute multiple times in the same application.


Class Destructors

Class destructors are the opposite of class constructors in that they perform the finalization of the class. Class destructors come with the same advantages as class constructors, except for finalization purposes.

The following example builds on the example shown in class constructors and introduces the finalization routine:

Note: The class destructor for a generic class or record may execute multiple times. The exact number of times the class destructor is executed in this case depends on the number of specialized versions of the generic type. For example, the class destructor for a specialized TList class may execute multiple times in the same application.

Message Methods

Message methods implement responses to dynamically dispatched messages. The message method syntax is supported on all platforms. VCL uses message methods to respond to Windows messages.

A message method is created by including the message directive in a method declaration, followed by an integer constant from 1 through 49151 that specifies the message > For message methods in VCL controls, the integer constant can be one of the Win32 message >Messages unit. A message method must be a procedure that takes a single var parameter.

A message method does not have to include the override directive to override an inherited message method. In fact, it does not have to specify the same method name or parameter type as the method it overrides. The message ID alone determines to which message the method responds and whether it is an override.

Implementing Message Methods

The implementation of a message method can call the inherited message method, as in the following example:

The inherited statement searches backward through the >DefaultHandler method originally defined in TObject.

The implementation of DefaultHandler in TObject simply returns without performing any actions. By overr >DefaultHandler , a >DefaultHandler method for controls calls the Win32 API DefWindowProc .

Message Dispatching

Message handlers are seldom called directly. Instead, messages are dispatched to an object using the Dispatch method inherited from TObject:

The Message parameter passed to Dispatch must be a record whose first entry is a field of type Word containing a message ID.

Dispatch searches backward through the >Dispatch calls DefaultHandler .

Является ли унаследованное ключевое слово, называемое автоматически в Delphi?

В соответствии с этой страницей

Он говорит: «Он вызывается в начале конструктора и в конце дескриптора. Это не обязательно, но рекомендуется в качестве хорошей практики».

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

Нет, inherited не вызывается автоматически; вы должны сделать это самостоятельно (если вы хотите вызвать унаследованную процедуру, которую вы обычно делаете). И вы даже можете выбрать, когда это сделать, см., Например, этот ответ. Обычно вы делаете это в начале конструктора и в конце деструктора.

Нет, это не то, что это значит; что он говорит, что вы можете не называть его, если у вас есть причина не называть его. Вы должны почти всегда вызывать inherited в каждом методе, который вы переопределяете, если только вам не нужно, чтобы что-то не произошло в вашем потомке, что родитель делает.

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

Delphi делает это очень легко; если ваш переопределенный метод имеет те же параметры, что и родители, вам даже не нужно передавать их:

6.5.3 Виртуальные методы

Классы, так же, как объекты могут иметь виртуальные методы. Однако между ними существует разница. Для объекта, достаточно пере объявить метод в объекте потомке с ключевым словом virtual , чтобы заменить его. Для класса, ситуация иная: виртуальные методы должны быть перекрыты с ключевым словом override . Несоблюдение этого правила, начнет новую серию виртуальных методов, скрывая предыдущую. Ключевое слово Inherited не будет вызывать метод предка, если были использованы virtual (виртуальные) методы.

Следующий код является неправильным :

Procedure MyProc ; virtual ;

ObjChild = Class ( ObjPArent )

Procedure MyProc ; virtual ;

Компилятор выдаст предупреждение:

Warning: An inherited method is hidden by OBJCHILD.MYPROC

Внимание: унаследованный метод скрыт OBJCHILD.MYPROC


Это код будет компилироваться, но с Inherited (унаследованными) методами будут происходить странные вещи.

Правильным объявлением классов, будет так:

Procedure MyProc ; virtual ;

ObjChild = Class ( ObjPArent )

Procedure MyProc ; override ;

Этот код будет компилироваться и запускаться без предупреждений или ошибок.

Если виртуальный метод действительно следует заменить методом с тем же именем, то нужно использовать ключевое слово reintroduce :

Procedure MyProc ; virtual ;

ObjChild = Class ( ObjPArent )

Procedure MyProc ; reintroduce ;

Метод MyProc больше не быдет виртуальным.

Для того, чтобы быть в состоянии сделать это, компилятор сохраняет для каждого класса — таблицу с виртуальных методов : VMT ( Virtual Method Table ) . Это таблица с указателями на каждый из виртуальных методов: каждый виртуальный метод имеет фиксированное положение в этой таблице (индекс) . Компилятор использует эту таблицу, чтобы определить, какой метод будет фактически использоваться во время выполнения. Когда объект потомка переопределяет метод, в VMT переписывается родительский метод. Более подробную информацию о VMT можно найти в Справочник программиста Free Pascal .

Ключевое слово virtual (виртуальный) можно заменить на ключевое слово dynamic (динамический) :динамические методы ведут себя так же, как виртуальные методы. В отличие от Delphi , в FPC реализация динамических методов сделана также как и реализация виртуальных методов.

Зарезервированные слова Delphi

var A, B : Integer; begin A:=3; B:=4; A:=A*A+B*B; end;

if (условие) then (действие) else (альтернатива) ;

Слова if (если), then (тогда), else (иначе) — зарезервированные. Действие и else альтернатива — это любые операторы Delphi, или несколько операторов, заключённых в логические скобки begin/end, или вызов подпрограммы. Если условие истинно, то выполняется действие, если ложно, то выполняется альтернатива.

(применяется, когда известно количество повторений цикла)

for счётчик := выражение-1 to выражение-2 do действие ;

Возможна работа оператора цикла, при котором переменная-счётчик будет не увеличиваться, а уменьшаться. В этом случае ключевое слово to заменяется на downto:

for счётчик := выражение-1 downto выражение-2 do действие ;

Цикл с предусловием (применяется, когда неизвестно количество повторений цикла)

while условие do тело цикла ;

Этот цикл будет выполняться до тех пор, пока истинно условие (логическое выражение, возвращающее значение типа Boolean). При этом если это выражение сразу равно false, тело цикла не будет выполнено ни разу. Нужно очень внимательно следить за написанием условия и контролем завершения цикла, так как в результате ошибки цикл while будет повторяться бесконечное количество раз, что приведёт к «зацикливанию» и «зависанию» программы.

Цикл с постусловием

(применяется, когда неизвестно количество повторений цикла)

repeat тело цикла until условие ;

Повторения сначала выполняет тело цикла, а затем уже проверяет выполнение условия: Таким образом, этот вариант цикла гарантирует, что тело цикла будет выполнен по крайней мере один раз. И будет выполняться до тех пор, пока условие не станет истинным (т.е. true). Стоит отметить, что это единственный оператор Delphi, в котором тело цикла не требуется заключать в логические скобки begin/end. Начало и конец тела цикла определяются по ключевым словам repeat и until.

Позволяет изменить последовательность выполнения программы. В качестве метки может использоваться любой допустимый идентификатор или число в диапазоне от 0 до 9999. Метку предварительно необходимо объявить в разделе описания переменных, но с помощью не ключевого слова var, а ключевого слова label:

Как вызвать метод предка в методе предка?

На сколько я понял есть:

И вы ожидаете получить в результате вызова метода DoA класса TMyObject2, исправив строку с пометкой fix
строку MyObject в консоли строку MyObject вместо MyObject2.

Ответ: это не возможно.

Решение:
1. Если классы написаны Вами — думать над проектированием.
2. Если классы написаны не Вами — думать о том, как их использовать. Возможно «патчить» исходники если совсем припёрло. В таком случае нужна существенно более подробная информация.

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL