Virtuální funkce (také známá jako virtuální metody) je členská funkce, která je deklarována v rámci základní třídy a je předefinována (přepsána) odvozenou třídou. Když odkazujete na objekt odvozené třídy pomocí ukazatele nebo odkazu na základní třídu, můžete pro tento objekt zavolat virtuální funkci a spustit verzi metody odvozené třídy.
- Virtuální funkce zajišťují volání správné funkce pro objekt bez ohledu na typ odkazu (nebo ukazatele) použitého pro volání funkce.
- Používají se hlavně k dosažení Runtime polymorfismu.
- Funkce jsou deklarovány pomocí a virtuální klíčové slovo v základní třídě.
- Řešení volání funkce se provádí za běhu.
Pravidla pro virtuální funkce
Pravidla pro virtuální funkce v C++ jsou následující:
- Virtuální funkce nemohou být statické.
- Virtuální funkce může být funkce přítele jiné třídy.
- Virtuální funkce by měly být přístupné pomocí ukazatele nebo reference typu základní třídy, aby se dosáhlo polymorfismu za běhu.
- Prototyp virtuálních funkcí by měl být stejný v základní i odvozené třídě.
- Jsou vždy definovány v základní třídě a přepsány v odvozené třídě. Není povinné, aby odvozená třída přepsala (nebo předefinovala virtuální funkci), v takovém případě se použije verze základní třídy funkce.
- Třída může mít virtuální destruktor, ale nemůže mít virtuální konstruktor.
Doba kompilace (časná vazba) Chování virtuálních funkcí VS runtime (pozdní vazba).
Zvažte následující jednoduchý program ukazující běhové chování virtuálních funkcí.
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->show();> >return> 0;> }> |
>
>
linux kterýVýstup
print derived class show base class>
Vysvětlení: Runtime polymorfismu je dosaženo pouze prostřednictvím ukazatele (nebo odkazu) typu základní třídy. Ukazatel základní třídy může také ukazovat na objekty základní třídy i na objekty odvozené třídy. Ve výše uvedeném kódu obsahuje ukazatel základní třídy ‚bptr‘ adresu objektu ‚d‘ odvozené třídy.
Pozdní vazba (Runtime) se provádí v souladu s obsahem ukazatele (tj. umístění, na které ukazuje ukazatel) a časná vazba (doba kompilace) se provádí podle typu ukazatele, protože funkce print() je deklarována pomocí virtuálního klíčové slovo, takže bude svázáno za běhu (výstup je třída odvozená z tisku protože ukazatel ukazuje na objekt odvozené třídy) a show() je nevirtuální, takže bude vázán během kompilace (výstup je zobrazit základní třídu protože ukazatel je základního typu).
Poznámka: Pokud jsme vytvořili virtuální funkci v základní třídě a ta je přepsána v odvozené třídě, pak nepotřebujeme virtuální klíčové slovo v odvozené třídě, funkce jsou automaticky považovány za virtuální funkce v odvozené třídě.
Práce s virtuálními funkcemi (koncept VTABLE a VPTR)
Jak je uvedeno zde, pokud třída obsahuje virtuální funkci, pak samotný kompilátor dělá dvě věci.
- Pokud je vytvořen objekt této třídy, pak a virtuální ukazatel (VPTR) je vložen jako datový člen třídy, aby ukazoval na VTABLE této třídy. Pro každý nový vytvořený objekt je vložen nový virtuální ukazatel jako datový člen dané třídy.
- Bez ohledu na to, zda je objekt vytvořen nebo ne, třída obsahuje jako člen statické pole ukazatelů funkcí s názvem VTABLE . V buňkách této tabulky jsou uloženy adresy každé virtuální funkce obsažené v dané třídě.
Zvažte příklad níže:

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;> }> |
>
>Výstup
base-1 derived-2 base-3 base-4>
Vysvětlení: Nejprve vytvoříme ukazatel typu základní třídy a inicializujeme jej adresou objektu odvozené třídy. Když vytvoříme objekt odvozené třídy, kompilátor vytvoří ukazatel jako datový člen třídy obsahující adresu VTABLE odvozené třídy.
Podobný koncept Pozdní a časná vazba se používá jako ve výše uvedeném příkladu. Pro volání funkce fun_1() se volá verze základní třídy funkce, fun_2() je přepsána v odvozené třídě, takže je volána verze odvozené třídy, fun_3() není v odvozené třídě přepsána a jde o virtuální funkci takže se volá verze základní třídy, podobně fun_4() není přepsáno, takže se volá verze základní třídy.
Poznámka: fun_4(int) v odvozené třídě se liší od virtuální funkce fun_4() v základní třídě, protože prototypy obou funkcí se liší.
Omezení virtuálních funkcí
- Pomalejší: Volání funkce trvá o něco déle kvůli virtuálnímu mechanismu a ztěžuje optimalizaci kompilátoru, protože přesně neví, která funkce bude v době kompilace volána. Obtížné ladění: Ve složitém systému mohou virtuální funkce trochu ztížit zjištění, odkud je funkce volána.