Accesso ai campi ereditati
La classe derivata puo` accedere ai membri protetti e pubblici della classe
base come se fossero suoi (e in effetti lo sono):
class Person {
public:
Person();
~Person();
void PrintData();
void Sleep();
private:
char* Name;
unsigned int Age;
/* ... */
};
/* Definizione dei metodi di Person */
class Student : Person {
public:
Student();
~Student();
void DoNothing(); // Metodo proprio di Student
private:
unsigned int IdCode;
/* ... */
};
void Student::DoNothing() {
Sleep(); // richiama Person::Sleep()
}
Il codice ereditato continua a comportarsi nella classe derivata esattamente
come si comportava nella classe base: se Person::PrintData() visualizzava
i membri Name e Age della classe Person ,
il metodo PrintData() ereditato da Student continuera`
a fare esattamente la stessa cosa, solo che riferira` agli attributi propri
dell'istanza di Student su cui il metodo verra` invocato.
In molti casi e` desiderabile che una certa funzione membro, ereditata dalla
classe base, si comporti diversamente nella classe derivata. Come alterare dunque
il comportamento (codice) ereditato? Tutto quello che bisogna fare e` ridefinire
il metodo ereditato; c'e` pero` un problema, non possiamo accedere direttamente
ai dati privati della classe base. Come fare?
Semplice riutilizzando il metodo che vogliamo ridefinire:
class Student : Person {
public:
Student();
~Student();
void DoNothing();
void PrintData(); // ridefinisco il metodo
private:
unsigned int IdCode;
/* ... */
};
void Student::PrintData() {
Person::PrintData();
cout << "Matricola: " << IdCode;
}
Poiche` cio` che desideriamo e` che PrintData() richiamato su
una istanza di Student visualizzi (oltre ai valori dei campi ereditati)
anche il numero di matricola, si ridefinisce il metodo in modo da richiamare
la versione ereditata (che visualizza i campi ereditati) e quindi si aggiunge
il comportamento (codice) da noi desiderato.
Si osservi la notazione usata per richiamare il metodo PrintData()
della classe Person , se avessimo utilizzato la notazione usuale
scrivendo
void Student::PrintData() {
PrintData();
cout << "Matricola: " << IdCode;
}
avremmo commesso un errore, poiche` il risultato sarebbe stato una chiamata
ricorsiva. Utilizzando il risolutore di scope (::) e il nome della classe
base abbiamo invece forzato la chiamata del metodo PrintData()
di Person .
Il linguaggio non pone alcuna limitazione circa il modo in cui PrintData()
(o una qualunque funzione membro ereditata) possa essere ridefinita, in particolare
avremmo potuto eliminare la chiamata a Person::PrintData() , ma
avremmo dovuto trovare un altro modo per accedere ai campi privati di Person .
Al di la` della fattibilita` della cosa, non sarebbe comunque buona norma agire
in tal modo, non e` bene ridefinire un metodo con una semantica differente.
Se Person::PrintData() aveva il compito di visualizzare lo stato
dell'oggetto, anche Student::PrintData() deve avere lo stesso
compito. Stando cosi` le cose, richiamare il metodo della classe base significa
ridurre la possibilita` di commettere un errore e risparmiare tempo e fatica.
E` per questo motivo infatti che non tutti i membri vengono effettivamente ereditati:
costruttori, distruttore, operatore di assegnamento e operatori di conversione
di tipo non vengono ereditati perche` la loro semantica e` troppo legata alla
effettiva struttura di una classe (il compilatore comunque continua a fornire
per la classe derivata un costruttore di default, uno di copia e un operatore
di assegnamento, esattamente come per una qualsiasi altra classe e con una semantica
prestabilita); il codice di questi membri e` comunque disponibile all'interno
della classe derivata (nel senso che possiamo richiamarli tramite il risolutore
di scope ::).
Naturalmente la classe derivata puo` anche definire nuovi metodi, compresa la
possibilita` di eseguire l'overloading di una funzione ereditata (naturalmente
la versione overloaded deve differire dalle precedenti per tipo e/o numero di
parametri). Infine non e` possibile ridefinire gli attributi (membri dato) della
classe base.
|