3. В описании объекта должен обязательно описываться специальный метод, инициализирующий объект (обычно ему дают название Init). В этом методе служебное слово PROCEDURE в объявлении и реализации должно быть заменено на слово CONSTRUCTOR. Это служебное слово обозначает особый вид процедуры — конструктор, который выполняет установочную работу для механизма виртуальных методов. Все типы объектов, которые имеют хотя бы один виртуальный метод, должны иметь конструктор. Конструктор всегда вызывается до первого вызова виртуального метода. Вызов виртуального метода без предварительного вызова конструктора может привести систему к тупиковому состоянию, а компилятор не проверяет порядок вызовов методов. Помните об этом!
13.5.2.2. Конструкторы и таблица виртуальных методов. Каждый экземпляр (переменная) типа «объект», содержащий виртуальные методы, должен инициализироваться отдельным вызовом конструктора. Если переменная А инициализирована вызовом конструктора, а переменная В того же типа не инициализирована, то присваивание В:=А не инициализирует переменную В и при вызове ее виртуальных методов программа может «зависнуть».
Чтобы понять, что делает конструктор, разберемся в механизме реализации виртуальных методов. Каждый объектный тип (именно тип, а не экземпляр) имеет «таблицу виртуальных методов» (VMT), которая содержит размер типа объекта и адреса кодов процедур или функций, реализующих каждый из его виртуальных методов. При вызове виртуального метода каким-либо экземпляром местонахождение кода реализации этого метода определяется по таблице VMT для типа этого экземпляра. Конструктор же устанавливает связь между экземпляром, который вызывает конструктор, и таблицей виртуальных методов данного объектного типа. Если же конструктор не будет вызван до обращения к виртуальному методу, то перед компьютером станет вопрос, где же искать этот метод. Это и приведет к тупиковой ситуации.
Важно помнить, что таблица виртуальных методов — одна для каждого типа, а не у каждой переменной типа «объект». Переменная лишь держит связь с таблицей своего типа, и эта связь устанавливается конструктором. В объекте может быть определено несколько конструкторов. Сами конструкторы могут быть только статическими, хотя внутри конструктора могут вызываться и виртуальные методы.
При передаче в процедуру или функцию полиморфного объекта, имеющего виртуальные методы, адреса этих методов передаются
- 284 -
через соответствующую объекту таблицу VMT. Это гарантирует, что сработают именно те методы, которые подразумевались при объявлении типа объекта. Кроме того, если объект Z наследует от объекта Y виртуальный метод, вызывающий другие методы, то последние вызовы будут относиться к методам объекта Z, а не Y. В случае статических методов все было бы наоборот (вызовы не «вернулись» бы в Z).
Мы начали этот раздел с примера полиморфной процедуры (см. рис. 13.6). Чтобы она заработала, надо сделать некоторые методы виртуальными и объявить конструкторы в объектах. Это проделано на рис. 13.7.
| USES CRT; { в примере используется системный модуль CRT }
| TYPE
| ObjPos = OBJECT
| Line : Word; { номер строки }
| Col : Word; { номер столбца }
| { ! } CONSTRUCTOR Init(init_line,init_col: Word);
| { ! } PROCEDURE Print; VIRTUAL { зарезервировано }
| END;
| CONSTRUCTOR ObjPos.Init( init_line, init_col : Word );
| BEGIN
| Line := init_line; { метод задания номера строки }
| Col := init_col; { метод задания номера столбца }
| END;
| PROCEDURE ObjPos.Print; { пустая процедура вывода }
| BEGIN
| Write( #7 ); { это вызовет звуковой сигнал }
| END;
| TYPE
| ObjSym = OBJECT( ObjPos ) { объявление наследования }
| Sym : Char; { поле-значение символа }
| { ! }CONSTRUCTOR Init(init_line,init_col : Word;
| init_sym : Char);
| {!} PROCEDURE Print; VIRTUAL {метод вывода Sym }
| END;
| CONSTRUCTOR ObjSym.Init;
| BEGIN
| ObjPos.Init( init_line, init_col ); { задание позиции }
| Sym := init_sym { задание значения символа }
| END;
Рис. 13.7
- 285 -
| PROCEDURE ObjSym.Print;
| BEGIN CRT.GotoXY( Col, Line );
| { процедура из модуля CRT }
| Write( Sym )
| { вывод символа в позиции }
| END;
| TYPE