logo

Virtuální funkce v C++

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í:

  1. Virtuální funkce nemohou být statické.
  2. Virtuální funkce může být funkce přítele jiné třídy.
  3. 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.
  4. Prototyp virtuálních funkcí by měl být stejný v základní i odvozené třídě.
  5. 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.
  6. 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.

  1. 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.
  2. 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:

virtuální ukazatel a virtuální tabulka

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.