Программирование >>  Решение нетривиальных задач 

1 ... 61 62 63 [ 64 ] 65 66 67 ... 77


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 ) Конструктор по умолчанию,

{ генерируемый компилятором.

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() .



1 ... 61 62 63 [ 64 ] 65 66 67 ... 77

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика