class Father { public: void MakeAThing(); }; class Child : public Father { public: void MakeAThing(); }; ... { Father *obj; obj = new Child(); obj->MakeAThing(); // Quale metodo verrà chiamato ? }Una spiegazione semplice (non stiamo ancora parlando di funzioni virtuali però questo esempio serve ad introdurre il discorso):
obj
è di tipo Father *
. La classe Child
è figlia della classe Father
(come consuetudine... Ndt: in inglese Child significa proprio
"figlio/figlia" e Father significa "padre"). Pertanto
è possibile scrivere un valore di tipo Child *
(quello ritornato
dall'operatore new) nella variabile obj
(sì come è possibile
scrivere un valore di tipo Child
in una variabile di tipo Father
).
Allora che succede? Quasi sicuramente penserai: siccome obj
è dichiarato
dalla classe Child
, sarà il metodo MakeAThing
della classe
Child
ad essere chiamato. Ma non è così.
Rifletti: il compilatore sa solo che obj
è di tipo Father *
,
perciò chiamerà il metodo di Father
. Infatti non c'è niente
che dica al compilatore che la funzione membro da chiamare dipende dalla classe.
Ebbene, esiste un modo per indicare questo al compilatore. Se dichiari le funzioni "overloaded" come
virtuali, tramite la keyword virtual
, indichi al compilatore, che
sa che questa funzione cambia da una classe ai suoi figli, di scegliere dinamicamente la funzione
da chiamare, in modo che corrisponda al tipo dell'oggetto. Confrontate questo esempio con quello
precedente:
class Father { public: virtual void MakeAThing(); }; class Child : public Father { public: virtual void MakeAThing(); }; ... { Father *obj; obj = new Child(); obj->MakeAThing(); // ora viene chiamato il metodo di Child ! }
Per dichiare una funzione come solo virtuale, aggiungi "= 0" alla fine della sua dichiarazione.
#include <stdio.h> class Father { public: virtual void Display() = 0; }; class Child : public Father { void Display() { printf ("Sono una figlia della Classe Father.\n"); } }; int main() { Father *f = new Father(); // NON PERMESSO (la classe Father è solo-virtuale) Child *c = new Child(); // OK. Child è una normale classe (non virtuale) Father *f = new Child(); // Puoi dichiarare dei puntatore a Father return 0; }Notare che si può definire un puntatore ad una classe astratta, ma non si può instanziarla.
Le classi astratte sono usate per dichiarare componenti comuni per le classi che ne
derivano. Quando erediti da una classe astratta in una classe, dopo aver definito un
suo membro dati (ehi, non definitelo private
!),
puoi usarlo in tutte le funzioni definite nelle classe figlie.
class Component { public: virtual void Display() = 0; }; class Rectangle : public Component { public: virtual void Display() { ... } }; class Square : public Component { public: virtual void Display() { ... } }; // Dichiara una lista di puntatori a Component Component *list[10]; ... { // Qual'è il bisogno di dichiarare un oggetto Component ? // Nessuno, sono i suoi figli che interessano list[0] = new Square; list[1] = new Rectangle; ... for (int i=0; i<10; i++) list[i]->Display(); // chiamata di funzione "dinamica" }Ndt: In questo caso la classe
Component
è una classe basa astratta,
che serve solo a riunire le proprietà (in tal caso un solo metodo, Display()
ma potrebbero esserci più metodi e campi dati) comuni all'intera gerarchia di classi che
da essa deriva. Il meccanismo delle funzioni virtuali, come mostrato nell'esempio precedente,
consente di avere una azione (come Display()
) che ha lo stesso nome su ogni oggetto
della gerarchia di classi su cui agisce, ma è implementata in modo diverso e il compilatore
sceglierà automaticamente la versione giusta da chiamare.
In virtù del polimorfismo è infatti possibile implementare liste eterogenee di oggetti (il vettore list
ne è un semplice esempio: è una lista statica di oggetti dinamici).
Nell'esempio viene chiamata prima la funzione Display()
di Square
,
poi quella di Rectangle
, ecc...