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


Виртуальный (кажущийся, гипотетический) метод имеет спецификатор (стандартную директиву) Virtual. Например:

Procedure Met2; Virtual;

Виртуальный метод предназначен для переопределения виртуального метода предшествующего предка. Недопустимо смешение статических и виртуальных методов при их переопределении. Ограничение: переопределяющие виртуаль­ные методы должны иметь точно такой же набор формальных параметров, как и самый первый виртуальный метод многоуровневой иерархии объектов.

Паскаль обеспечивает вызов (связывание) виртуального метода программы на этапе выполнения программы. Это называют поздним связыванием. При позднем связывании полиморфизм распространяется не только от текущего уровня иерархии вниз, к потомкам (как для статических методов), но и вверх, к предкам.

В соответствии с этим основные отличия переопределения виртуальных методов от переопределения статических методов состоят в следующем:

1) если для экземпляра потомка методы предка вызывают другие виртуальные
методы, имена которых есть в объекте-предке и в объекте-потомке, то вы­
зываются методы потомка;

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

Компилятор не устанавливает связи объекта с виртуальным методом. Вме­сто этого он создает специальную таблицу виртуальных методов (ТВМ, VMT -Virtual Method Table). Для каждого типа объекта создается своя ТВМ; каждый экземпляр объекта использует эту ТВМ, единственную для данного типа вир­туальных объектов. В каждой ТВМ содержится размер данного типа объекта в байтах. ТВМ любого объекта доступна через скрытый параметр Self, содер­жащий адрес ТВМ, который передается методу при вызове.

Связывание каждого экземпляра объекта и его ТВМ осуществляется с по­мощью конструктора на этапе выполнения программы. Это специальный ме­тод, подобный обычной процедуре, но в заголовке вместо PROCEDURE стоит слово CONSTRUCTOR. Если объектный тип содержит виртуальный метод, то он должен содержать хотя бы один конструктор. Каждый экземпляр объекта должен инициализироваться отдельным вызовом конструктора. Конструктор инициализирует экземпляр объекта и устанавливает для него значение адреса его ТВМ. Экземпляр объекта содержит только адрес ТВМ, а не саму ТВМ.

Вызов конструктора должен предшествовать вызову любого виртуального метода для обработки данного экземпляра объекта. Обращение к виртуально­му методу до вызова конструктора вызовет ошибку на этапе выполнения про­граммы, так как нет связи экземпляра объекта с его ТВМ.

При отладке программы можно использовать директиву компилятора {$R+}. При этом компилятор будет проверять, инициализирован ли экземп­ляр объекта, вызывающий виртуальный метод. Если в процессе проверки инициализации экземпляра объекта, вызывающего виртуальный метод, будет обнаружен вызов метода до инициализации объекта конструктором, выдается сообщение о фатальной ошибке 210:

Object not initialized - объект не инициализирован.

Так как директива {$R+} замедляет выполнение программы, после отлад­ки директиву надо удалить; по умолчанию работает директива {$R->.

В объекте может быть сколько угодно конструкторов. Конструктор не мо­жет быть виртуальным. Конструктор может быть только статическим и может быть переопределен. Конструкторы наследуются так же, как и другие стати­ческие методы. Из конструктора можно вызывать и виртуальные методы.

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

Constructor TA.TNIT ;

Begin

End;

Конструктору принято давать имя INIT. На практике в качестве конструк­тора используют метод, который устанавливает некоторые начальные значения экземпляра объекта. В конструкторе может происходить выделение ОП из ку­чи, если поля данных динамические, и необходимая инициализация полей дан­ных (в том числе и вызовы конструкторов-предков для унаследованных полей).

Пример программы с использованием виртуальных методов дан в листинге 4. Это вариант программы листинга 3, но с виртуальным методом Met2.

Листинг 4.Использование виртуальных методов.

Program virt1; {$F+,R+} Uses Crt;

Type ObjName1 = object { - объявление объекта-предка } Fl1 : integer;

Constructor Met1; { - конструктор }

Procedure Met2; Virtual; { - виртуальный метод } End; ObjName2 = object(ObjName1){- объявление потомка}

Procedure Met2; Virtual; { - виртуальный метод } End;

{ -- Методы объекта ObjName1 ------ }

Constructor ObjName1.Met1; Begin Fl1 := 12;

Met2; { - вызов Met2 из конструктора }

End;

Procedure ObjNamel.Met2;

Begin

Writeln ( 'Работает метод ObjName1.Met2: FL1 = ', Fl1) End;

{ -- Методы объекта ObjName2 ------ }

Procedure ObjName2.Met2; Begin Fl1 := 34;

Writeln ( 'Работает метод ObjName2.Met2: FL1 = ', Fl1) End;

Var VI:ObjName1;{- переменная объектного типа - предка }
V2 : ObjName2; { - " " " потомка }

{ ------ Основная программа ---------- }

Begin ClrScr;

Assign (Output, '2virt.res'); Rewrite (Output);

Writeln ( 'ОБЪЕКТЫ, ВИРТУАЛЬНЫЕ МЕТОДЫ' );

Writeln ('Работаем с VI - экземпляром типа предка'); VI.Met1; { - вызывается конструктор Met1 для экземпляра VI - предка }

{ Met1 вызывает метод ObjName1.Met2 - предка }

VI.Met2; { - непосредственно вызывается метод ObjName1.Met2; }

Writeln ('Работаем с V2 - экземпляром типа потомка'); V2.Met1; { - вызывается конструктор Met1 для экземпляра V2 - потомка VI; Met1 вызывает метод ObjName2.Met2 - потомка -в этом достоинство виртуальных методов }

V2.Met2 { - непосредственно вызывается метод ObjName2.Met2; }

Close. (Output) ;

End.

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

Var Obj1, Obj2 : TObj; { - объявление переменных } Begin

Obj1.Init; { - инициализация Obj1 }

Obj2 := Obj1; { - недопустимо до инициализации Obj2

с помощью Obj2.Init; }

End.

Хорошим стилем ООП является использование процедур инициализации предков. То есть если потомок имеет новую процедуру инициализации, то в ней обычно сначала вызывается процедура инициализации (например, конст­руктор) непосредственного предка, а затем выполняется своя. Это естествен­ный способ проинициализировать наследуемые поля предназначенным для этого методом.

Дополнительная возможность вызова метода, непосредственно наследуемо­го объектом-потомком, - использование ключевого слова Inherited (насле­дуемый) перед именем вызываемого метода. Например, Objl непосредствен­ный предок объекта Obj2. В состав Objl входит метод Metl. Вызов этого метода объекта Objl из метода Obj2.Met2 может быть задан явно в виде:

Procedure Obj2.Met2;

Begin Inherited Met1 (A1, A2);

{ Это эквивалентно вызову: Obj1.Met1 (A1, A2); }

End;

При этом не надо знать имя объекта-предка (Objl). Основные правила использования виртуальных методов:

1) если тип объекта-предка описывает метод как виртуальный, то все его по­томки, которые реализуют свой метод с тем же именем, должны описать
этот метод как виртуальный. Нельзя виртуальный метод заменить статическим. Иначе компилятор выдаст сообщение об ошибке:

Error 149: VIRTUAL expected - ОЖИДАЕТСЯ СЛОВО VIRTUAL.

2) если переопределяется виртуальный метод, то заголовок переопределяемо­
го метода не может быть изменен; должны остаться неизменными: количество, последовательность и типы формальных параметров в одноименных
виртуальных методах. Если этот метод - функция, то не должен изменяться и тип результата. При нарушении этого правила компилятор выдаст сообщение:

131: Header does not match previous definition - заголовок не соответствует предыдущему определению;

3) в описании объекта, имеющего виртуальные методы, обязателен -конструктор. Он устанавливает работу механизма виртуальных методов. Вызов виртуального метода без предварительного вызова конструктора может при­
вести к тупиковому состоянию. Чтобы избежать этого, на период отладки
надо включить директиву компилятора {$R+};

4) каждый экземпляр объекта должен инициализироваться отдельным вызо­вом конструктора;

5) количество конструкторов может быть любым;

6) конструктор должен быть статическим и может быть переопределен.



Дата добавления: 2019-12-09; просмотров: 686;


Поиск по сайту:

Воспользовавшись поиском можно найти нужную информацию на сайте.

Поделитесь с друзьями:

Считаете данную информацию полезной, тогда расскажите друзьям в соц. сетях.
Poznayka.org - Познайка.Орг - 2016-2024 год. Материал предоставляется для ознакомительных и учебных целей.
Генерация страницы за: 0.012 сек.