|
Программирование >> Решение нетривиальных задач
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() .
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |