Принцип роботи віртуальних функцій
При створені об'єкту похідного класу він складається з двох частин, частини базового класу та власної, обидві частини нерозривні. При створенні в похідному класі перевизначеного варіанту віртуальної функції об'єкт слідкує за нею самостійно. Компілятор створює таблицю віртуальних функцій яку називають V-таблиця вказівник на яку зберігається на об'єкті. Такий вказівник називається vptr-вказівник на таблицю віртуальних функцій. Він ініціалізується при створенні об'єкту похідного класу в частині що відповідає базовому класу. Коли викликається деструктор похідного класу і до об'єкту додається друга частина з похідного класу то вказівник vptr коригується так щоб вказати на віртуальні функції, які перевизначені в похідному класі. В результаті при використанні вказівника на базовий клас вказівник vptr посилається на той варйант віртуальної функції, який відповідає реальному типу об'єкту. Функція Speak з класу Dog. Якщо об'єкт похідного класу володіє власною функцією, яка відсутня в базовому класі, то отримати до неї доступ через вказівник базового класу неможливо. Оскільки така функція не є віртуальною, то викликати її можна тільки з об'єкту похідного класу. Віртуальні функції викликаються при звертанні до них через вказівник або посилання. Якщо вказівник на об'єкт похідного класу буде видалений, то спрацює деструктор класу, який необхідно оголосити, як віртуальний. Після його спрацювання автоматично викличеться деструктор базового класу, об'єкт буде повністю видалений. Правило: якщо в класі оголошені віртуальні функції, то і деструктор має бути віртуальним. Конструктори та конструктори копій не можуть бути віртуальними, але іноді необхідно передати вказівник на об'єкт базового класу, або отримати копію об'єкту похідного класу. Цю проблему вирішують за допомогою віртуального методу clone() , його оголошують в базовому класі. Він створює нову копію поточного об'єкту та повертає її як об'єкт. Оскільки метод clone() є віртуальним, то кожний похідний клас перевизначає його, тому при виклику метода clone() створюється копія об'єкту відповідного похідного класу.
class Mammal{
public:
virtual Mammal* Clone(){return new Mammal (*this);}
};
class Dog:public Mammal{
public:
Mammal *Clone(){retrun new Dog(*this)};
};
Недоліком віртуальних функцій вважається ресурсні затрати на V-таблицю, та оголошення деструктора віртуальним.
Множини наслідування
Приклад: необхідно створити клас, що походить від двох класів "птахи" та "ссавці". Клас птахів включає функцію літати fly(); Клас Mammal має похідний клас "кінь", який включає 2 фукнції "ржати " і "скакати" необхідно створити клас "пегас", який би міг літати та ржати та скакати. При використанні одиночного наслідування клас "пегас" міг би бути похідним від класу птахи. Але тоді він не володіє функціями "ржати " і "галоп". І навпаки, якщо пегас похідний від класу "кінь", то він не вміє літати.
Варіанти розв'язку:
- скопіювати ф-цію літати в клас "кінь" та зробити клас "пегас" похідним від класу "кінь". Але тоді при будь-яких змінах у ф-ції fly їх необхідно робити в двох екземплярах.
- переіменувати ф-цію галоп класу кін у ф-цію move та перевизначити її в класі пегас, так що б вона виконувала ф-цію ф-ції fly. Тоді в класі кінь ф-ція move виконує задачі ф-ції галоп, а вкласі пегас задачі ф-ції fly та ф-ції галоп.
-перенести ф-цію fly в клас кінь, але це збільшує к-сть ф-цій в базовому класі. Таку ф-цію необхідно оголосити віртуальною
class Horse{
public:
void Gallop();
void Winny();
virtual void Fly();
};
class Pegasus:public Horse{
public:
void Fly(...);
}; //то всьо гавно, читай то шо нижче
Всі ці рішення були в рамках одиночного наслідування.
Новий клас можна отримати від декількох базових класів. Для цього використовують множинне наслідування. При оголошенні базові класи перелічуються з типом наслідування через кому
class Pegasus:public Horse, public Bird{...};
Кожен раз при створенні об'єкту класу пегас створюється частина класу кінь і частина класу птах. При видаленні об'єкту деструктори спрацьовують в протилежному порядку. Деструктори базових класів оголошуються віртуальними. Якщо клас наслідує елементи з однаковими іменами але з різних класів, то в похідному класі необхідно використовувати специфікатори класів для того, щоб розрізняти ці елементи.
class A{
public:
int F();
};
class B{
public:
int F();
};
class C: public A, public B{
A::F();
B::F();
};
Інакше компілятор видасть повідомлення про неоднозначність використання.
Віртуальний базовий клас
Якщо обидва класи походять від одного класу, які в свою чергу мають спільний базовий клас то це може призвести до невизначенсті. В мові С++ є можливість вказати, що ідеться не про 2 базових класи тварин, а про один спільний клас. Для цього клас тварин необхідно оголосити віртуальним для похідних класів кінь та птах. Оголошується віртуальність наслідування. При віртуальному наслідуванні змінні основного базового класу ініціалізуються конструктором класу, який є останнім в ієрархії
class Animal{...};
class Horse:Virtual public Animal{...};
class Bird:virtual public Animal{...};
class Pegasus: public Horse, public Bird{...};
При використанні віртуальних базових класів неоднозначність імені вирішується правилом домінуваня: ім'я в похідному класі домінує над таким самим іменем в будь-якому базовому класі. Ім'я що домінує використовується без специфікатора класу. Імена в базових класах використовуються зі специфікатором класу.
clsass B{
public:
int Q();
};
class C : virtual public B{
public:
Q();
};
class D:public C{...};
class F:public D{
B::Q(); //з класу B
Q(); //з класу С
};
Ф-ція Q класу B домінує в класі в . Тому в класі F виклик ф-ції Q за іменем призведе до виклику цієї функції з класу С. Для виклику ф-ції Q з класу B необхідно вкзати спепицифікатор класу. Не всі компілятори С++ підтримують множинне наслідування. Проміжним рішенянм між одиночним та множинним наслідуванням є використання класу можливостей. Такий клас додає лише нові функціональні можливості але не включає ніяких даних. Функції класу можливостей передаються в похідні класи через звичайне наслідування. Новими функціоанальмими можливостями можуть бути функції зображення об'єкту. Якщо клас забезпечує лише інтерфейси для похідних класів, то він називається абстрактним типом даних. Він завжди буде базовим по відношенню до інших класів.
Чисті віртуальні функції
Чисті віртуальні функції це віртуальні функції, які ініціалізуються нульовим значенням.
virtual void
Будь-який клас, що місить чисто віртуальну функцію називаєтсья абстрактним. Для абстракного класу не можна створювати об'кти. Його можна лише наслідувати, а чисту віртуальну функцію перевизначати в похідних класах. К-сть оголошених ЧВФ(чисті віртуальні функції) в абстрактному класі і к-сть перевизначених ЧВФ в похідних класах має співпадати, інакше похідний клас також стане абстрактним.
прога від вови....
Переважно ЧВФ в абстрактному класі не реалізуються їх завдання полягає у визначенні та забезпеченні інтерфейсу похідних класів. Рівень абстракції залежить від ступеня деталізації. Рекомендується використовувати абстрактні типи даних при створенні загального інтерфейсу для всіх похідних класів.