logo

Funzione virtuale in C++

Una funzione virtuale (nota anche come metodi virtuali) è una funzione membro dichiarata all'interno di una classe base e ridefinita (sostituita) da una classe derivata. Quando fai riferimento a un oggetto di classe derivata utilizzando un puntatore o un riferimento alla classe base, puoi chiamare una funzione virtuale per quell'oggetto ed eseguire la versione del metodo della classe derivata.

  • Le funzioni virtuali garantiscono che venga chiamata la funzione corretta per un oggetto, indipendentemente dal tipo di riferimento (o puntatore) utilizzato per la chiamata della funzione.
  • Vengono utilizzati principalmente per ottenere il polimorfismo runtime.
  • Le funzioni sono dichiarate con a virtuale parola chiave in una classe base.
  • La risoluzione di una chiamata di funzione viene eseguita in fase di esecuzione.

Regole per le funzioni virtuali

Le regole per le funzioni virtuali in C++ sono le seguenti:

  1. Le funzioni virtuali non possono essere statiche.
  2. Una funzione virtuale può essere una funzione amico di un'altra classe.
  3. È necessario accedere alle funzioni virtuali utilizzando un puntatore o un riferimento al tipo di classe base per ottenere il polimorfismo di runtime.
  4. Il prototipo delle funzioni virtuali dovrebbe essere lo stesso sia nella classe base che in quella derivata.
  5. Sono sempre definiti nella classe base e sovrascritti in una classe derivata. Non è obbligatorio che la classe derivata sovrascriva (o ridefinisca la funzione virtuale), in tal caso viene utilizzata la versione della classe base della funzione.
  6. Una classe può avere un distruttore virtuale ma non può avere un costruttore virtuale.

Comportamento in fase di compilazione (associazione anticipata) rispetto al runtime (associazione tardiva) delle funzioni virtuali

Considera il seguente semplice programma che mostra il comportamento in fase di esecuzione delle funzioni virtuali.



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->stampa();> >// Non-virtual function, binded at compile time> >bptr->mostra();> >return> 0;> }>

>

>

Produzione

print derived class show base class>

Spiegazione: Il polimorfismo di runtime si ottiene solo tramite un puntatore (o riferimento) del tipo di classe base. Inoltre, un puntatore alla classe base può puntare agli oggetti della classe base così come agli oggetti della classe derivata. Nel codice precedente, il puntatore della classe base 'bptr' contiene l'indirizzo dell'oggetto 'd' della classe derivata.

L'associazione tardiva (Runtime) viene eseguita in base al contenuto del puntatore (ovvero la posizione puntata dal puntatore) e l'associazione anticipata (Tempo di compilazione) viene eseguita in base al tipo di puntatore poiché la funzione print() è dichiarata con il virtual parola chiave quindi verrà associata in fase di esecuzione (l'output è classe derivata dalla stampa poiché il puntatore punta all'oggetto della classe derivata) e show() non è virtuale, quindi verrà associato durante la fase di compilazione (l'output è mostra la classe base poiché il puntatore è di tipo base).

gestore attività per linux

Nota: Se abbiamo creato una funzione virtuale nella classe base e viene sovrascritta nella classe derivata, non abbiamo bisogno di una parola chiave virtuale nella classe derivata, le funzioni vengono automaticamente considerate funzioni virtuali nella classe derivata.

Funzionamento delle Funzioni Virtuali (concetto di VTABLE e VPTR)

Come discusso qui, se una classe contiene una funzione virtuale, il compilatore stesso fa due cose.

  1. Se viene creato un oggetto di quella classe, allora a puntatore virtuale (VPTR) viene inserito come membro dati della classe per puntare al VTABLE di quella classe. Per ogni nuovo oggetto creato, viene inserito un nuovo puntatore virtuale come membro dati di quella classe.
  2. Indipendentemente dal fatto che l'oggetto sia stato creato o meno, la classe contiene come membro un array statico di puntatori a funzione chiamato VTABLE . Le celle di questa tabella memorizzano l'indirizzo di ciascuna funzione virtuale contenuta in quella classe.

Considera l'esempio seguente:

cm in piedi e pollici

puntatore virtuale e tabella virtuale

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

>

>

Produzione

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

Spiegazione: Inizialmente creiamo un puntatore alla classe base del tipo e lo inizializziamo con l'indirizzo dell'oggetto della classe derivata. Quando creiamo un oggetto della classe derivata, il compilatore crea un puntatore come membro dati della classe contenente l'indirizzo di VTABLE della classe derivata.

Un concetto simile di Rilegatura tardiva e anticipata viene utilizzato come nell'esempio precedente. Per la chiamata alla funzione fun_1(), viene chiamata la versione della classe base della funzione, fun_2() viene sovrascritto nella classe derivata quindi viene chiamata la versione della classe derivata, fun_3() non viene sovrascritto nella classe derivata ed è una funzione virtuale quindi viene chiamata la versione della classe base, allo stesso modo fun_4() non viene sovrascritta, quindi viene chiamata la versione della classe base.

Nota: fun_4(int) nella classe derivata è diverso dalla funzione virtuale fun_4() nella classe base poiché i prototipi di entrambe le funzioni sono diversi.

Limitazioni delle funzioni virtuali

    Più lento: la chiamata alla funzione richiede leggermente più tempo a causa del meccanismo virtuale e rende più difficile l'ottimizzazione per il compilatore perché non sa esattamente quale funzione verrà chiamata in fase di compilazione. Difficile da eseguire il debug: in un sistema complesso, le funzioni virtuali possono rendere un po' più difficile capire da dove viene chiamata una funzione.