Правила программирования на Си и Си++



         

137. Виртуальная функция не является виртуальной, если вызывается из конструктора или деструктора - часть 3


_vtable = _employee_vtable; // Создается таблица виртуальных

}                           // функций.

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

Когда вы создаете во время выполнения объект таким образом:

storable *p = new employee();

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

storable *p;

p = (storable *)malloc( sizeof(employee) );

_employee_ctor( p );

Вызов _employee_ctor()

сначала инициализирует компонент базового класса посредством вызова _sortable_ctor(), которая добавляет таблицу этой виртуальной функции к своей таблице и выполняется. Затем управление передается обратно к _employee_ctor() и указатель в таблице виртуальной функции переписывается так, чтобы он указывал на таблицу производного класса.

Отметьте, что, хотя p теперь указывает на employee, код p->print()

генерирует точно такой же код, как и раньше:

( p->_vtable[0] )( p );

Несмотря на это, теперь p

указывает на объект производного класса, поэтому вызывается версия print()

из производного класса (так как _vtable в объекте производного класса указывает на таблицу производного класса). Крайне необходимо, чтобы эти две функции print()

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

Возвращаясь к основному смыслу данного правила, отметим, что при рассмотрении того, как работает конструктор, важен порядок инициализации. Конструктор производного класса перед тем, как он что-либо сделает, вызывает конструктор базового класса. Так как _vtable в конструкторе базового класса указывает на таблицу виртуальных функций базового класса, то вы лишаетесь доступа к виртуальным функциям базового класса после того, как вызвали их. Вызов print в конструкторе базового класса все так же дает:

( this->_vtable[0] )( p );

но _vtable

указывает на таблицу базового класса и _vtable[0]

указывает на функцию базового класса. Тот же самый вызов в конструкторе производного класса даст версию print()

производного класса, потому что _vtable

будет перекрыта указателем на таблицу производного класса к тому времени, когда была вызвана print().

Хотя я и не показывал этого прежде, то же самое происходит в деструкторе. Первое, что делает деструктор, — это помещает в _vtable

указатель на таблицу своего собственного класса. Только после этого он выполняет написанный вами код. Деструктор производного класса вызывает деструктор базового класса на выходе (в самом конце — после того, как выполнен написанный пользователем код).




Содержание  Назад  Вперед