Екземпляр класу в С++ є неперервною областю в пам'яті. Покажчик на такий об'єкт містить її початкову адресу. Коли викликається функція-член (об'єкту посилається повідомлення), виклик компілюється у звичайний виклик функції із додатковим аргументом, який містить покажчик на об'єкт. Якщо наявний виклик функції
classname*object;
object->message(15);,
то компілятором він перетворюється на
classname_message(object,15);
Механізм віртуальних функцій у С++ забезпечується за допомогою таблиць віртуальних функцій (ТВФ), які мають такі властивості:
1. ТВФ будується компілятором автоматично для кожного класу, який має віртуальні функції й містить їх адреси, доступні в цьому класі.
2. Кожний екземпляр класу містить скритий покажчик на його ТВФ.
3. Компілятор автоматично вставляє в початок конструктора класу фрагмент коду, який ініціалізує покажчик на ТВФ кожного класу.
4. Для будь-якої ієрархії класів адреса деякої віртуальної функції має одне й те саме зміщення у ТВФ кожного класу.
5. Під час виклику віртуальних функцій код, згенерований компілятором, перш за все знаходить покажчик віртуальної таблиціi, потім відбувається звертання до ТВФ і знаходиться адреса віртуальної функції, i лише після цього відбувається її безпосередній виклик. Наприклад:
|
|
Class Parent
{int value;
public:
virtual int method1(float r);
virtual void method2(void);
virtual float method3(char*s);};
Class child1:public Parent
{public:
virtual void method2(void);}
Class child2:public child1
{public:
virtual float method3(char*s);}
Для цього прикладу ТВФ можна зобразити так:
//клас Parent:
virtual_table1->
Parent::method1
Parent::method2
Parent::method3
//для Child1:
virtual_table2->
Parent::method1
Child1::method2
Parent::method3
//Беремо до уваги однаковість зміщення.
//для Child2:
virtual_table3=>
Parent::method1
Child1::method2
Child2::method3
Тоді виклик вигляду
child2*c;
c->method3(string");
компілятор перетворює на
(*(c=>virtual_table3[2]))(c,"string");
Віртуальні базові класи
Ієрархії класів та успадкування. Похідні та їх базові класи утворюють ієрархію, яка може бути надзвичайно складною навіть у відносно простих програмах. А якщо згадати ще й можливості множинного успадкування, то ситуація взагалі може вийти з-під контролю. Не дивно, що у складних ієрархіях класів виникають конфлікти. Крім уже розглянутих вище конфліктів імен, виникають і конфлікти дещо іншого характеру. Розглянемо такий приклад:
Class A
{protected:
int data;
public:
void func(void){//тіло}};
Class B: public A
{//...}
ClassC:public A
{//...}