logo

Віртуальна функція в C++

Віртуальна функція (також відома як віртуальні методи) — це функція-член, яка оголошена в базовому класі та перевизначена (перевизначена) похідним класом. Коли ви посилаєтеся на об’єкт похідного класу за допомогою покажчика або посилання на базовий клас, ви можете викликати віртуальну функцію для цього об’єкта та виконати версію методу похідного класу.

  • Віртуальні функції забезпечують виклик правильної функції для об’єкта, незалежно від типу посилання (або покажчика), який використовується для виклику функції.
  • Вони в основному використовуються для досягнення поліморфізму часу виконання.
  • Функції оголошуються за допомогою a віртуальний ключове слово в базовому класі.
  • Вирішення виклику функції виконується під час виконання.

Правила для віртуальних функцій

Правила для віртуальних функцій у C++ такі:

  1. Віртуальні функції не можуть бути статичними.
  2. Віртуальна функція може бути функцією друга іншого класу.
  3. Доступ до віртуальних функцій має здійснюватися за допомогою вказівника або посилання типу базового класу для досягнення поліморфізму часу виконання.
  4. Прототип віртуальних функцій має бути однаковим як у базовому, так і в похідному класі.
  5. Вони завжди визначені в базовому класі та перевизначені в похідному класі. Для похідного класу не обов’язково перевизначати (або перевизначати віртуальну функцію), у цьому випадку використовується версія базового класу функції.
  6. Клас може мати віртуальний деструктор, але не може мати віртуальний конструктор.

Поведінка віртуальних функцій під час компіляції (раннє зв’язування) проти часу виконання (пізнє зв’язування).

Розглянемо наступну просту програму, яка показує поведінку віртуальних функцій під час виконання.



C++




// C++ program to illustrate> // concept of Virtual Functions> #include> using> namespace> std;> class> base {> public>:> >virtual> void> print() { cout <<>'print base class '>; }> >void> show() { cout <<>'show base class '>; }> };> class> derived :>public> base {> public>:> >void> print() { cout <<>'print derived class '>; }> >void> show() { cout <<>'show derived class '>; }> };> int> main()> {> >base* bptr;> >derived d;> >bptr = &d;> >// Virtual function, binded at runtime> >bptr->print();>> >// Non-virtual function, binded at compile time> >bptr->показати ();> >return> 0;> }>

>

>

Вихід

print derived class show base class>

Пояснення: Поліморфізм виконання досягається лише за допомогою покажчика (або посилання) типу базового класу. Крім того, покажчик базового класу може вказувати як на об’єкти базового класу, так і на об’єкти похідного класу. У наведеному вище коді покажчик базового класу «bptr» містить адресу об’єкта «d» похідного класу.

Пізнє зв’язування (час виконання) виконується відповідно до вмісту вказівника (тобто розташування, на яке вказує вказівник), а раннє зв’язування (час компіляції) виконується відповідно до типу вказівника, оскільки функція print() оголошується з віртуальним ключове слово, щоб воно було прив’язане під час виконання (вихід є надрукувати похідний клас оскільки вказівник вказує на об’єкт похідного класу), а show() є невіртуальним, тому він буде зв’язаний під час компіляції (вихід є показати базовий клас оскільки вказівник має базовий тип).

Примітка: Якщо ми створили віртуальну функцію в базовому класі, і вона перевизначається в похідному класі, тоді нам не потрібне ключове слово virtual у похідному класі, функції автоматично вважаються віртуальними функціями в похідному класі.

програма java hello

Робота віртуальних функцій (концепція VTABLE і VPTR)

Як обговорювалося тут, якщо клас містить віртуальну функцію, то сам компілятор виконує дві речі.

  1. Якщо створено об’єкт цього класу, то a віртуальний покажчик (VPTR) вставляється як член даних класу, щоб вказати на VTABLE цього класу. Для кожного створеного нового об’єкта новий віртуальний покажчик вставляється як член даних цього класу.
  2. Незалежно від того, створений об’єкт чи ні, клас містить як член статичний масив покажчиків на функції під назвою VTABLE . Комірки цієї таблиці зберігають адресу кожної віртуальної функції, що міститься в цьому класі.

Розглянемо приклад нижче:

віртуальний покажчик і віртуальна таблиця

покажчик розіменування c

C++




// C++ program to illustrate> // working of Virtual Functions> #include> using> namespace> std;> class> base {> public>:> >void> fun_1() { cout <<>'base-1 '>; }> >virtual> void> fun_2() { cout <<>'base-2 '>; }> >virtual> void> fun_3() { cout <<>'base-3 '>; }> >virtual> void> fun_4() { cout <<>'base-4 '>; }> };> class> derived :>public> base {> public>:> >void> fun_1() { cout <<>'derived-1 '>; }> >void> fun_2() { cout <<>'derived-2 '>; }> >void> fun_4(>int> x) { cout <<>'derived-4 '>; }> };> int> main()> {> >base* p;> >derived obj1;> >p = &obj1;> >// Early binding because fun1() is non-virtual> >// in base> >p->fun_1();>> >// Late binding (RTP)> >p->fun_2();>> >// Late binding (RTP)> >p->fun_3();>> >// Late binding (RTP)> >p->fun_4();>> >// Early binding but this function call is> >// illegal (produces error) because pointer> >// is of base type and function is of> >// derived class> >// p->fun_4(5);> >return> 0;> }>

>

>

Вихід

base-1 derived-2 base-3 base-4>

Пояснення: Спочатку ми створюємо вказівник базового класу типу та ініціалізуємо його адресою об’єкта похідного класу. Коли ми створюємо об’єкт похідного класу, компілятор створює вказівник як член даних класу, що містить адресу VTABLE похідного класу.

Схожа концепція Пізнє та раннє зв'язування використовується, як у наведеному вище прикладі. Для виклику функції fun_1() викликається версія базового класу функції, fun_2() перевизначається в похідному класі, тому викликається версія похідного класу, fun_3() не перевизначається в похідному класі та є віртуальною функцією тому викликається версія базового класу, подібним чином fun_4() не перевизначається, тому викликається версія базового класу.

Примітка: fun_4(int) у похідному класі відрізняється від віртуальної функції fun_4() у базовому класі, оскільки прототипи обох функцій різні.

Обмеження віртуальних функцій

    Повільніше: виклик функції займає трохи більше часу через віртуальний механізм і ускладнює оптимізацію для компілятора, оскільки він не знає, яку саме функцію буде викликано під час компіляції. Важко налагодити: у складній системі через віртуальні функції може бути трохи складніше визначити, звідки виконується виклик функції.