Un oggetto di una classe derivata è anche un membro
di una classe di base. Per esempio, il programma Capo virtuale,
l'oggetto Capo è anche un oggetto Nemico. Tutto ciò
ha senso in quando si tratta solo di un tipo di nemico speciale.
Inoltre ha senso in quanto l'oggetto Capo ha tutti i membri
dell'oggetto Nemico. Bene, e allora? Poiché una classe
derivata è anche un membro della classe di base, si può usare un
puntatore alla classe di base per puntare un oggetto della classe
derivata. Questo è ciò che faccio in main() con la seguente
istruzione, la quale istanzia un oggetto Capo su heap e crea
un puntatore a Nemico che punti all'oggetto Capo.
Nemico* pCattivo = new
Capo();
Cosa vuol dire tutto questo? E' utile a permetterci
di gestire l'oggetto senza doverne conoscere il tipo esatto. Per
esempio di può avere una funzione che accetta un puntatore a Nemico
e può funzionare sia con Nemico che con Capo oggetti. La funzione
non deve conoscere l'esatta tipologia di oggetto passato, può
funzionare con oggetti che producono risultati diversi a seconda di
quale sia utilizzato. Ma ora analizziamo cosa accade nella successiva
riga di codice.
pCattivo - > Fastidio();
Invece mostra una linea di testo Il nemico vuole
combatterti. Quindi non è stata
richiamata la funzione Fastidio()
dell'oggetto Capo ma
dell'oggetto Nemico,
nonostante sia stata superata dalla funzione Fastidio()
nella classe di Capo.
Questo accade come risultato di un legame precedente nel
quale l'esatta funzione è legata al tipo di puntatore, in questo
caso Nemico. Ciò di
cui si ha bisogno è una funzione membro chiamata sul tipo di oggetto
puntato, e non fissato dal puntatore. In questo si ottiene la
flessibilità del legame successivo
attraverso funzioni virtuali,
che ottiene con il comportamento polimorfico. Per creare una funzione
membro virtuale, si aggiunge semplicemente la parola chiave virtual
prima del nome della funzione nella sua dichiarazione. Questo è ciò
che viene fatto in Nemico
con la seguente istruzione.
void virtual VFastidio()
const
{ cout << “Il
nemico vuole combatterti.\n”; }
Questo significa che VFastidio() è una
funzione virtuale. E' virtuale in Nemico, e viene ereditata in
Capo. Questo impila che VFastidio() in Capo
possa superare con la seguente istruzione la funzione ereditata. La
corretta versione di VFastidio() viene chiamata (in base al
tipo di oggetto) e non sarà fissata dal tipo di puntatore.
void virtual VFastidio()
const
{ cout << “Il capo
vuole mettere fine alla tua esistenza.\n”; }
La prova del comportamento polimorfico viene in
main() con la seguente istruzione, nel quale la funzione
VFastidio() definita in Capo viene chiamata ed il testo
“Il capo vuole mettere fine alla tua esistenza”. Viene mostrato
su schermo.
pCattivo - > VFastidio();
Definire Distruttori virtuali
Quando si usa un puntatore alla classe di base per
puntare un oggetto di una classe derivata, si può avere un
potenziale problema. Quando viene cancellato il puntatore, solo il
distruttore della classe viene chiamato per l'oggetto. Questo può
condurre a risultati disastrosi poiché il distruttore della classe
derivata è necessario per liberare la memoria ( come fa il
distruttore di Capo). La soluzione come si può indovinare, è
di rendere il distruttore della classe virtuale. In questo modo viene
chiamato il distruttore della classe, il qual conduce (sempre) alla
chiamata del distruttore della classe di base, dando ad ogni classe
la possibilità di ripulirsi. La teoria viene messa in pratica quando
si crea il distruttore virtuale della classe Nemico.
virtual ~Nemico(){ cout <<
“m_pDanno cancellato”; delete m_pDanno;}
Quando viene cancellato il puntatore che punta alla
classe Capo con la seguente istruzione, il distruttore della
classe Capo viene chiamato, il quale libera la memoria su heap
dei dati m_pMultidanno e mostra il messaggio.
delete pCattivo;
Quindi viene chiamato il distruttore di Nemico,
il quale libera la memoria su heap da m_pDanno e mostra
il messaggio m_pDanno cancellato. L'oggetto è distrutto, e
tutte le memorie associate ad esso sono cancellate.