Поліморфізм та віртуальні функції.
Завантажити презентаціюПрезентація по слайдам:
Поліморфізм та віртуальні функції. Похідні класи мають з базовим класом зв'язки двох видів. Перший з них полягає в тому, що екземпляри похідних класів використовують всі відкриті члени базового класу – зокрема методи базового класу. class Base { public: void meth () { cout
Другий вид зв'язку полягає в тому, що: екземпляр базового класу можна створити як екземпляр похідного або присвоїти йому значення екземпляру похідного; посилання на базовий клас може посилатись на похідний; вказівник на базовий клас може вказувати на похідний. Всі ці операції виконуються без явного приведення типів і є реалізацією відношення «is-a». int main (void) { Base b; SubBase sb; // екземпляр базового класу створюється як похідний Base bb = SubBase (); // екземпляру базового класу присвоюється похідний b = sb; // посилання на базовий клас посилається на похідний Base & bbb = sb; // вказівник на базовий клас вказує на похідний Base *p = &sb; // sb = b; // таке присвоєння неможливе! return 0; }
Цілком зрозуміла заборона присвоєнь у зворотному напрямку – адже якщо екземпляр похідного класу створюється як базовий, то виникає проблема із викликом методів похідного класу, яких немає у базовому: class Base { public: void meth () { cout
Та обставина, що посилання та вказівники базового класу можуть вказувати на екземпляри похідних класів, приводить до низки цікавих можливостей – зокрема методи, які мають параметрами посилання або вказівник на базовий клас, можуть викликатись із аргументами-екземплярами похідних класів: void fun (Base & b) // параметр – посилання на // базовий клас { b.meth(); } int main (void) { Base b; SubBase sb; fun (b); // такий виклик можливий fun (sb); // і такий теж можливий return 0; } Але в будь-якому разі, функція fun() викликатиме метод meth() базового класу.
Проте, можлива ситуація, коли успадковані методи похідних класів повинні поводити себе інакше, ніж методи базового класу. Така поведінка називається “поліморфною”. (Поліморфний – такий, що має багато форм). Реалізація поліморфного спадкування здійснюється одним із двох способів. 1. Перевизначення методів базового класу у похідному класі (заміщення методів) : class Base { public: void meth () { cout
В усіх попередніх прикладах зв'язування екземпляру із конкретним методом (функцією-членом класу) відбувалось на етапі компіляції (тобто ще до початку виконання програми). Ця процедура, як відомо, називається раннім зв'язуванням. Альтернативний спосіб – пізнє зв'язування (інколи – динамічне зв'язування, в С# - динамічний поліморфізм) дозволяє асоціювати об'єкт із методом вже під час виконання програми. 2. Використання віртуальних методів . Пізнє зв'язування охоплює ряд функцій-членів (методів), які називаються віртуальними функціями. Віртуальна функція (virtual) оголошується в базовому класі і перевизначається у похідних класах. Сукупність класів, в яких визначається і перевизначається віртуальна функція, називається поліморфним кластером. У межах цього кластеру об'єкт пов'язується із конкретною віртуальною функцією-членом під час виконання програми. Звичайна функція-член також може бути перевизначена у похідному класі, як у попередньому прикладі. Проте без атрибуту virtual до неї буде застосоване лише раннє зв'язування.
Тепер, якщо визначити зовнішню функцію fun (Base & b), як у попередньому прикладі, то ми побачимо реалізацію пізнього зв'язування: void fun (Base & b) { b.virt(); } int main (void) { Base b; SubBase sb; fun (b); // виклик методу virt()базового класу fun (sb);// виклик методу virt()похідного класу return 0; } Рішення про те, який саме метод virt() базового чи похідного класу має бути викликаний у функції fun, приймається під час виконання програми – це пізнє звязування.
Віртуальні методи можуть перевантажуватись, як звичайні функції: class Base { public: virtual void virt () // віртуальний метод { cout
Слід зазначити, що використання пізнього зв'язування достатньо складний механізм, який вимагає суттєвих витрат пам'яті. Тому віртуальними слід робити лише такі функції, які дійсно будуть перевизначатись у похідних класах. Зауваження. Конструктори не можуть бути віртуальними – адже похідний клас не спадкує конструктор базового. А от деструктор може бути віртуальним. Користь віртуального деструктора показує наступний приклад, висновком з якого може бути правило: якщо клас не вимагає явного виконання деструктора, краще визначити віртуальний деструктор, навіть якщо йому не має чого робити.
Приклад. class Base { public: // раніше визначені члени класу char name [20]; virtual ~Base () // віртуальний деструктор {cout
Повернемось ще раз до перевизначення функцій. Якщо в похідному класі визначається метод, одноіменний з віртуальним методом базового класу, але з відмінною сигнатурою, він перекриває всі віртуальні методи базового класу. Це означає, що в похідному класі вони не доступні. class Base { public: // раніше визначені члени класу virtual virt (); // віртуальний метод }; class SubBase : public Base { public : // раніше визначені члени класу virt (int i); // метод - перекриває віртуальний }; int main (void) { Base b = Base ("Base"); SubBase sb = SubBase ("SubBase"); sb (10); // припустимо sb (); // помилка – метод базового класу недоступний return 0; }
Абстрактний базовий клас (ABC – Abstract Base Class). Наразі нам відомі правила простого спадкування та більш складного поліморфного спадкування, яке включає використання віртуальних функцій. Наступний рівень складності – абстрактний базовий клас. Необхідність в ньому виникає, коли необхідно описати об'єкти, що мають східну природу, проте їх важко визначити як базовий та похідний класи. Наприклад, розглядаючи такі об'єкти, як прямокутник та ромб, неможливо встановити між ними відношення «Є» (“is-a”), хоча й очевидно, що вони мають багато спільного: наприклад, поняття площі, повороту на площині. У таких випадках необхідно виділити у об'єктів все спільне і створити клас, який буде базовим для них всіх. Якщо реалізація окремих функцій можлива лише на рівні похідних класів, у базовому їх визначають як чисто віртуальні функції (pure virtual function ). Екземпляри такого базового класу неможливо створити, сам клас називається абстрактним і використовується лише для створення похідних класів.
Приклад. class Figure // клас абстрактний – він має чисто віртуальну функцію { protected : double x_cnt, y_cnt; // координати центру фігури public: Figure (double x=0, double y=0) : x_cnt (x), y_cnt (y) {} // чисто віртуальна функція: virtual double Square () const = 0; }; class Rectangle : public Figure // похідний клас – прямокутник { private : double leng, width; public : Rectangle (double l=0,double w=0,double x=0,double y=0); double Square () const { return leng*width; } }; class Rhombus : public Figure // похідний клас – ромб { private : double len, angle; public : Rhombus (double l = 0,double a = 0,double x =0,double y =0); double Square () const { return len*len*sin(angle); } };
Схожі презентації
Категорії