Анимация
JavaScript
|
Главная Библионтека 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 |