Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 [ 63 ] 64 65 66 67 68 69 70 71 72 73 74 75 76

storable::storable ( void ) { stuff = 0; }

void storable::print( void ){/*материал для отладки print */ }

void storable::virtf( void ){/* делай что-нибудь */ }

int storable::nonvirtual( void ){ }

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

int storable print ( storable *this ){/*...*/}

int storable virtf ( storable *this ){/*...*/}

int storable nonvirtual ( storable *this ) {/*...*/}

typedef void (* vtab[])(...); массив указателей на функции

vtab storable vtab

storable print,

storable virtf,

NULL метка-заполнитель для функции сравнения

typedef struct storable

storable vtab * vtable; int stuff;

storable;

storable ctor( void ) конструктор

vtable = storable vtable; Эту строку добавляет

компилятор.

stuff =0; Эта строка из исходного кода.

Когда вы вызываете невиртуальную функцию, используя такой код, как:

storable *p; p->nonvirtual();

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

storable nonvirtual( p )

Если вы вызываете виртуальную функцию, подобную этой:

p->print();

то получаете нечто совершенно отличное:

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

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



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

class employee : public storable

int derived stuff; ... public:

virtual int cmp( const storable &r );

/* виртуальн1й */ int employee::print( const storable &r){} /* виртуальн1й */ int employee::cmp ( const storable &r){}

А вот что сделает с ним компилятор:

int employee print( employee *this ) {/*...*/}

int employee cmp ( employee *this, const storable *ref r )

{/*...*/}

vtab employee vtable =

employee print,

storable virtf, Тут нет замещения в производном классе, поэтому используется указатель на функцию базового класса.

employee cmp

typedef struct employee

vtab * vtable; Генерируемое компилятором поле данных, int stuff; Поле базового класса,

int derived stuff; Поле, добавленное в объявлении производного класса.

employee;

employee ctor( employee *this ) Конструктор по умолчанию,

{ генерируем1й компилятором. storable ctor(); Базовые классы инициализируются

в первую очередь. 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 указатель на таблицу своего собственного класса. Только после этого он выполняет написанный вами код. Деструктор производного класса



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 [ 63 ] 64 65 66 67 68 69 70 71 72 73 74 75 76