runtime polymorphism c
Детальне вивчення поліморфізму виконання в C ++.
Поліморфізм часу виконання також відомий як динамічний поліморфізм або пізнє зв'язування. У поліморфізмі часу виконання виклик функції вирішується під час виконання.
На відміну від цього, для компіляції часу або статичного поліморфізму компілятор виводить об'єкт під час виконання, а потім вирішує, який виклик функції прив'язувати до об'єкта. У C ++ поліморфізм виконання виконується з використанням методу, що перевизначає.
У цьому підручнику ми детально вивчимо все про поліморфізм виконання.
=> Перевірте ВСІ підручники з C ++ тут.
Що ви дізнаєтесь:
- Заміна функції
- Віртуальна функція
- Робота віртуальної таблиці та _vptr
- Чисто віртуальні функції та абстрактний клас
- Віртуальні деструктори
- Висновок
- Рекомендована література
Заміна функції
Перевизначення функції - це механізм, за допомогою якого функція, визначена в базовому класі, ще раз визначається у похідному класі. У цьому випадку ми говоримо, що функція замінена у похідному класі.
Слід пам’ятати, що перевизначення функції неможливо виконати в класі. Функція замінена лише у похідному класі. Отже, успадкування повинно бути присутнім для перевизначення функції.
Друга річ полягає в тому, що функція базового класу, яку ми перевизначаємо, повинна мати однаковий підпис або прототип, тобто вона повинна мати те саме ім'я, той самий тип повернення та той самий список аргументів.
Побачимо приклад, який демонструє перевизначення методу.
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'< Вихід:
Клас :: База
Клас :: Виведено
У наведеній вище програмі ми маємо базовий клас та похідний клас. У базовому класі ми маємо функцію show_val, яка замінена у похідному класі. У головній функції ми створюємо об'єкт кожного класу Base та Derived і викликаємо функцію show_val з кожним об'єктом. Це дає бажаний результат.
Вищевказане прив'язування функцій з використанням об'єктів кожного класу є прикладом статичного прив'язки.
Тепер давайте подивимося, що відбувається, коли ми використовуємо вказівник базового класу і призначаємо похідні об'єкти класу як його вміст.
Приклад програми показано нижче:
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'; } }; class Derived:public Base { public: void show_val() //overridden function { cout <<'Class::Derived'; } }; int main() { Base* b; //Base class pointer Derived d; //Derived class object b = &d; b->show_val(); //Early Binding }
Вихід:
Клас :: База
Тепер ми бачимо, що результат - “Class :: Base”. Отже, незалежно від того, який тип об’єкта містить базовий вказівник, програма виводить вміст функції класу, базовим вказівником якого є тип. У цьому випадку здійснюється також статичне зв'язування.
Для того, щоб зробити вихід базового вказівника, правильним вмістом і належним зв'язком, ми використовуємо динамічне прив'язування функцій. Це досягається за допомогою механізму віртуальних функцій, який пояснюється в наступному розділі.
найкраща програма для приховування ip адреси
Віртуальна функція
Оскільки перевизначена функція повинна бути динамічно прив’язана до тіла функції, ми робимо функцію базового класу віртуальною, використовуючи ключове слово “virtual”. Ця віртуальна функція - це функція, яка замінена в похідному класі, і компілятор виконує пізнє або динамічне прив'язування для цієї функції.
Тепер давайте модифікуємо вищезазначену програму, щоб включити віртуальне ключове слово наступним чином:
#include using namespace std;. class Base { public: virtual void show_val() { cout << 'Class::Base'; } }; class Derived:public Base { public: void show_val() { cout <<'Class::Derived'; } }; int main() { Base* b; //Base class pointer Derived d; //Derived class object b = &d; b->show_val(); //late Binding }
Вихід:
Клас :: Виведено
Отже, у наведеному вище визначенні класу Base, ми зробили функцію show_val як 'віртуальну'. Оскільки функція базового класу робиться віртуальною, коли ми призначаємо похідний об’єкт класу покажчику базового класу і викликаємо функцію show_val, прив’язка відбувається під час виконання.
Таким чином, оскільки покажчик базового класу містить похідний об’єкт класу, тіло функції show_val у похідному класі прив’язане до функції show_val, а отже, і результату.
У C ++ перевизначена функція у похідному класі також може бути приватною. Компілятор перевіряє тип об'єкта лише під час компіляції та прив'язує функцію під час виконання, отже, це не робить різниці, навіть якщо функція є загальнодоступною або приватною.
Зверніть увагу, що якщо функція оголошена віртуальною в базовому класі, то вона буде віртуальною у всіх похідних класах.
Але дотепер ми не обговорювали, як саме віртуальні функції відіграють роль у визначенні правильної функції, яка має бути пов'язана, або іншими словами, як насправді відбувається пізнє прив'язування.
Віртуальна функція точно пов'язана з тілом функції під час виконання за допомогою концепції віртуальна таблиця (VTABLE) і прихований вказівник під назвою _vptr.
Обидві ці концепції є внутрішньою реалізацією і не можуть використовуватися безпосередньо програмою.
найкраще безкоштовне програмне забезпечення для налаштування ПК -
Робота віртуальної таблиці та _vptr
Спочатку давайте зрозуміємо, що таке віртуальна таблиця (VTABLE).
Компілятор під час компіляції встановлює по одній VTABLE для класу, що має віртуальні функції, а також класи, які походять від класів, що мають віртуальні функції.
VTABLE містить записи, які є вказівниками на віртуальні функції, які можуть викликати об'єкти класу. Для кожної віртуальної функції існує один запис покажчика функції.
У випадку чисто віртуальних функцій цей запис має значення NULL. (Це причина, чому ми не можемо створити екземпляр абстрактного класу).
Наступна сутність, _vptr, яка називається покажчиком vtable, є прихованим покажчиком, який компілятор додає до базового класу. Цей _vptr вказує на vtable класу. Усі класи, похідні від цього базового класу, успадковують _vptr.
Кожен об'єкт класу, що містить віртуальні функції, внутрішньо зберігає цей _vptr і є прозорим для користувача. Кожен виклик віртуальної функції за допомогою об'єкта потім вирішується за допомогою цього _vptr.
Візьмемо приклад, щоб продемонструвати роботу vtable та _vtr.
#include using namespace std; class Base_virtual { public: virtual void function1_virtual() {cout<<'Base :: function1_virtual()
';}; virtual void function2_virtual() {cout<<'Base :: function2_virtual()
';}; virtual ~Base_virtual(){}; }; class Derived1_virtual: public Base_virtual { public: ~Derived1_virtual(){}; virtual void function1_virtual() { coutfunction2_virtual(); delete (b); return (0); }
Вихід:
Виведено1_virtual :: function1_virtual ()
База :: function2_virtual ()
У наведеній вище програмі ми маємо базовий клас із двома віртуальними функціями та віртуальним деструктором. Ми також отримали клас із базового класу і в цьому; ми замінили лише одну віртуальну функцію. У основній функції похідний покажчик класу призначається базовому покажчику.
Потім ми викликаємо обидві віртуальні функції за допомогою покажчика базового класу. Ми бачимо, що замінена функція викликається, коли вона викликається, а не базова функція. Тоді як у другому випадку, оскільки функція не замінена, викликається функція базового класу.
Тепер давайте подивимося, як представлена вище програма представлена внутрішньо за допомогою vtable та _vptr.
Відповідно до попереднього пояснення, оскільки існує два класи з віртуальними функціями, ми матимемо дві vtables - по одній для кожного класу. Крім того, _vptr буде присутній для базового класу.

Вище показано зображене зображення того, яким буде макет vtable для вищезазначеної програми. Vtable для базового класу є простим. У випадку похідного класу замінюється лише function1_virtual.
Звідси ми бачимо, що у похідному класі vtable вказівник на функцію function1_virtual вказує на перевизначену функцію у похідному класі. З іншого боку, покажчик функції для function2_virtual вказує на функцію в базовому класі.
Таким чином, у наведеній вище програмі, коли базовому вказівнику присвоюється похідний об'єкт класу, базовий вказівник вказує на _vptr похідного класу.
Отже, коли здійснюється виклик b-> function1_virtual (), викликається function1_virtual з похідного класу, а коли здійснюється виклик функції b-> function2_virtual (), оскільки цей вказівник на функцію вказує на функцію базового класу, функцію базового класу це називається.
Чисто віртуальні функції та абстрактний клас
Детальніше про віртуальні функції в C ++ ми бачили у нашому попередньому розділі. У C ++ ми також можемо визначити “ чисто віртуальна функція ”, Яке зазвичай прирівнюється до нуля.
Чиста віртуальна функція оголошена, як показано нижче.
virtual return_type function_name(arg list) = 0;
Клас, який має принаймні одну чисто віртуальну функцію, яка називається “ абстрактний клас '. Ми ніколи не можемо створити екземпляр абстрактного класу, тобто ми не можемо створити об’єкт абстрактного класу.
Це тому, що ми знаємо, що запис робиться для кожної віртуальної функції у VTABLE (віртуальна таблиця). Але у випадку чисто віртуальної функції цей запис не має жодної адреси, що робить його неповним. Отже, компілятор не дозволяє створювати об’єкт для класу з неповним записом VTABLE.
Це причина, з якої ми не можемо створити екземпляр абстрактного класу.
У наведеному нижче прикладі буде продемонстровано чисту віртуальну функцію, а також клас Abstract.
#include using namespace std; class Base_abstract { public: virtual void print() = 0; // Pure Virtual Function }; class Derived_class:public Base_abstract { public: void print() { cout <<'Overriding pure virtual function in derived class
'; } }; int main() { // Base obj; //Compile Time Error Base_abstract *b; Derived_class d; b = &d; b->print(); }
Вихід:
Перевизначення чистої віртуальної функції у похідному класі
У наведеній вище програмі ми маємо клас, визначений як Base_abstract, який містить чисто віртуальну функцію, яка робить його абстрактним класом. Потім ми виводимо клас “Виведений_клас” з Base_abstract і замінюємо чисту віртуальну функцію друку в ньому.
У головній функції не коментується перший рядок. Це тому, що якщо ми прокоментуємо це, компілятор видасть помилку, оскільки ми не можемо створити об’єкт для абстрактного класу.
Але другий рядок далі код працює. Ми можемо успішно створити вказівник базового класу, а потім призначити йому похідний об’єкт класу. Далі ми викликаємо функцію друку, яка виводить вміст функції друку, замінений у похідному класі.
Коротко перелічимо деякі характеристики абстрактного класу:
- Ми не можемо створити екземпляр абстрактного класу.
- Абстрактний клас містить принаймні одну чисто віртуальну функцію.
- Хоча ми не можемо створити екземпляр абстрактного класу, ми завжди можемо створити покажчики або посилання на цей клас.
- Абстрактний клас може мати деякі реалізації, такі як властивості та методи, поряд із чисто віртуальними функціями.
- Коли ми отримуємо клас з абстрактного класу, похідний клас повинен замінити всі чисті віртуальні функції в абстрактному класі. Якщо це не вдалося зробити, то похідний клас також буде абстрактним класом.
Віртуальні деструктори
Деструктори класу можна оголосити віртуальними. Щоразу, коли ми робимо upcast, тобто присвоюючи похідний об'єкт класу покажчику базового класу, звичайні деструктори можуть давати неприйнятні результати.
Наприклад,розглянемо наступне оновлення звичайного деструктора.
#include using namespace std; class Base { public: ~Base() { cout << 'Base Class:: Destructor
'; } }; class Derived:public Base { public: ~Derived() { cout<< 'Derived class:: Destructor
'; } }; int main() { Base* b = new Derived; // Upcasting delete b; }
Вихід:
Базовий клас :: Деструктор
У наведеній вище програмі ми отримали успадкований похідний клас від базового класу. В основному ми призначаємо об'єкт похідного класу покажчику базового класу.
В ідеалі, деструктор, який викликається при виклику “delete b”, повинен був бути похідним класом, але з результатів ми бачимо, що деструктор базового класу викликається як вказівник базового класу на це.
Через це деструктор похідного класу не викликається, а похідний об'єкт класу залишається цілим, що призводить до витоку пам'яті. Рішенням цього є зробити конструктор базового класу віртуальним, щоб вказівник на об’єкт вказував на правильний деструктор та здійснював належне знищення об’єктів.
Використання віртуального деструктора показано в наведеному нижче прикладі.
#include using namespace std; class Base { public: virtual ~Base() { cout << 'Base Class:: Destructor
'; } }; class Derived:public Base { public: ~Derived() { cout<< 'Derived class:: Destructor
'; } }; int main() { Base* b = new Derived; // Upcasting delete b; }
Вихід:
Похідний клас :: Destructor
Базовий клас :: Деструктор
Це та сама програма, що і попередня програма, за винятком того, що ми додали віртуальне ключове слово перед деструктором базового класу. Зробивши деструктор базового класу віртуальним, ми досягли бажаного результату.
Ми бачимо, що коли ми призначаємо об’єкт похідного класу покажчику базового класу, а потім видаляємо вказівник базового класу, деструктори викликаються в зворотному порядку створення об’єкта. Це означає, що спочатку викликається деструктор похідного класу, а об’єкт знищується, а потім об’єкт базового класу знищується.
найкращий засіб для чищення ПК для Windows 7
Примітка: У C ++ конструктори ніколи не можуть бути віртуальними, оскільки конструктори беруть участь у побудові та ініціалізації об'єктів. Отже, нам потрібно, щоб усі конструктори були виконані повністю.
Висновок
Поліморфізм виконання виконується з використанням перевизначення методу. Це чудово працює, коли ми викликаємо методи з відповідними об'єктами. Але коли ми маємо вказівник базового класу і ми викликаємо перевизначені методи, використовуючи вказівник базового класу, що вказує на похідні об’єкти класу, через статичне зв’язування виникають несподівані результати.
Щоб подолати це, ми використовуємо концепцію віртуальних функцій. Завдяки внутрішньому представленню vtables та _vptr, віртуальні функції допомагають нам точно викликати потрібні функції. У цьому підручнику ми детально бачили поліморфізм середовища виконання, що використовується в C ++.
Цим ми закінчуємо наші навчальні посібники з об’єктно-орієнтованого програмування на C ++. Ми сподіваємось, що цей підручник буде корисним для кращого і глибшого розуміння об’єктно-орієнтованих концепцій програмування на C ++.
=> Завітайте сюди, щоб вивчити C ++ з нуля.
Рекомендована література
- Поліморфізм у C ++
- Спадкування в C ++
- Функції друзів в C ++
- Класи та об'єкти в C ++
- Використання класу Selenium Select для обробки випадаючих елементів на веб-сторінці - Підручник селену №13
- Підручник з основних функцій Python з практичними прикладами
- Віртуальна машина Java: як JVM допомагає у запуску програми Java
- Як налаштувати файли сценарію LoadRunner VuGen та параметри виконання