logo

Funció virtual en C++

Una funció virtual (també coneguda com a mètodes virtuals) és una funció membre que es declara dins d'una classe base i que es torna a definir (substitueix) per una classe derivada. Quan feu referència a un objecte de classe derivada mitjançant un punter o una referència a la classe base, podeu cridar una funció virtual per a aquest objecte i executar la versió del mètode de la classe derivada.

  • Les funcions virtuals asseguren que es crida la funció correcta per a un objecte, independentment del tipus de referència (o punter) utilitzat per a la trucada de funció.
  • S'utilitzen principalment per aconseguir el polimorfisme en temps d'execució.
  • Les funcions es declaren amb a virtual paraula clau en una classe base.
  • La resolució d'una trucada de funció es fa en temps d'execució.

Regles per a les funcions virtuals

Les regles per a les funcions virtuals en C++ són les següents:

  1. Les funcions virtuals no poden ser estàtiques.
  2. Una funció virtual pot ser una funció amiga d'una altra classe.
  3. S'ha d'accedir a les funcions virtuals utilitzant un punter o referència del tipus de classe base per aconseguir el polimorfisme en temps d'execució.
  4. El prototip de funcions virtuals hauria de ser el mateix tant a la classe base com a la classe derivada.
  5. Sempre es defineixen a la classe base i es substitueixen en una classe derivada. No és obligatori que la classe derivada anul·li (o torni a definir la funció virtual), en aquest cas, s'utilitza la versió de classe base de la funció.
  6. Una classe pot tenir un destructor virtual però no pot tenir un constructor virtual.

Comportament de temps de compilació (enllaç primerenc) VS temps d'execució (enllaç tardà) de les funcions virtuals

Considereu el següent programa senzill que mostra el comportament en temps d'execució de les funcions virtuals.



C++

mides de lletra de làtex




// 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->mostrar();> >return> 0;> }>

>

Bubble sort python
>

Sortida

print derived class show base class>

Explicació: El polimorfisme en temps d'execució només s'aconsegueix mitjançant un punter (o referència) del tipus de classe base. A més, un punter de classe base pot apuntar als objectes de la classe base així com als objectes de la classe derivada. Al codi anterior, el punter de classe base 'bptr' conté l'adreça de l'objecte 'd' de la classe derivada.

L'enllaç tardà (temps d'execució) es fa d'acord amb el contingut del punter (és a dir, la ubicació apuntada pel punter) i l'enllaç anticipat (temps de compilació) es fa segons el tipus de punter, ja que la funció print() es declara amb el virtual. paraula clau de manera que s'enllaçarà en temps d'execució (la sortida és classe derivada d'impressió ja que el punter apunta a l'objecte de la classe derivada) i show() no és virtual, de manera que estarà lligat durant el temps de compilació (la sortida és mostrar la classe base ja que el punter és de tipus base).

Nota: Si hem creat una funció virtual a la classe base i s'està substituint a la classe derivada, no necessitem una paraula clau virtual a la classe derivada, les funcions es consideren automàticament funcions virtuals a la classe derivada.

Funcionament de Funcions Virtuals (concepte de VTABLE i VPTR)

Com es comenta aquí, si una classe conté una funció virtual, el compilador fa dues coses.

  1. Si es crea un objecte d'aquesta classe, a punter virtual (VPTR) s'insereix com a membre de dades de la classe per apuntar a la VTABLE d'aquesta classe. Per a cada objecte nou creat, s'insereix un punter virtual nou com a membre de dades d'aquesta classe.
  2. Independentment de si l'objecte es crea o no, la classe conté com a membre una matriu estàtica de punters de funció anomenada VTABLE . Les cel·les d'aquesta taula emmagatzemen l'adreça de cada funció virtual continguda en aquesta classe.

Considereu l'exemple següent:

punter virtual i taula virtual

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;> }>

>

>

Sortida

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

Explicació: Inicialment, creem un punter del tipus classe base i l'iniciem amb l'adreça de l'objecte de classe derivada. Quan creem un objecte de la classe derivada, el compilador crea un punter com a membre de dades de la classe que conté l'adreça de VTABLE de la classe derivada.

Gràfic de números romans 1100

Un concepte semblant de Enquadernació tardana i anticipada s'utilitza com a l'exemple anterior. Per a la crida a la funció fun_1(), s'anomena la versió de classe base de la funció, fun_2() s'invalida a la classe derivada, de manera que s'anomena la versió de classe derivada, fun_3() no es substitueix a la classe derivada i és una funció virtual. de manera que s'anomena la versió de la classe base, de la mateixa manera fun_4() no s'invalida, de manera que s'anomena la versió de la classe base.

Nota: fun_4(int) a la classe derivada és diferent de la funció virtual fun_4() a la classe base, ja que els prototips d'ambdues funcions són diferents.

Limitacions de les funcions virtuals

    Més lenta: la crida a la funció triga una mica més a causa del mecanisme virtual i dificulta l'optimització del compilador perquè no sap exactament quina funció es cridarà en temps de compilació. Difícil de depurar: en un sistema complex, les funcions virtuals poden fer que sigui una mica més difícil esbrinar des d'on s'està cridant una funció.