Quando vuoi usare tutti i membri di una classe padre senza doverli dichiarare di nuovo uno alla volta, basta mettere una sola dichiarazione nell'header della classe "figlia":
class father { public: int a; }; class A : public father { public: int b; };In questo esempio, la classe A avrà un membro pubblico chiamato
b
,
come al solito. Ma poichè è dichiarata come figlia della classe
father
, avrà anche un membro a
dichiarato implicitamente.
Quando una classe eredita da un'altra, ne eredita tutti i membri. Questo porta a due problemi:
private
, protected
e public
class A { int a; public: int GetA() { return a; } };Infatti, puoi dichiarare ogni membro (variabile) privato. Nessuno sarà in grado di utilizzarli direttamente. Questa è una buona pratica nella programmazione orientata agli oggetti.
Ndt: qui ovviamente si consiglia di non permettere l'accesso diretto ai membri dati, tranne che
in casi rari. Infatti nella maggior parte dei casi i membri dati devono essere gestiti esclusivamente
tramite i metodi (le funzioni) dell'oggetto e una loro gestione esterna impropria può portare
a delle situazioni di incoerenza nell'oggetto e quindi di errori, oltre che rendere i programmi molto
meno "mantenibili".
I membri funzione ovviamente devono essere public se si vuole poter mandare i corrispondenti
"messaggi" all'oggetto dal mondo esterno.
Se una funzione invece serve solo ad uso interno dell'oggetto ed è pericoloso e/o inutile
metterla a disposizione del mondo esterno (non corrisponde ad alcun messaggio) meglio non
renderla public, ma private
o protected
a seconda dei casi.
Un'altra cosa: ho provato questo codice con tutti i miei compilatori e lo hanno accettato senza problemi, senza manco sputar fuori un warning:
class A { int a; public: int & GetA() { return a; } }; int main() { A unA; unA.GetA() = 5; return 0; }Come potete vedere tracciando il programma e guardando i contenuti dell'oggetto A, la chiamata
unA.GetA()
ritorna il precedente valore di a (che è indefinito
all'inizio); per la verità essa ritorna un riferimento a quel valore. Un
riferimento è qualcosa di analogo ad un puntatore C che viene però
automaticamente referenziato (vedi riferimenti), perciò l'assegnazione è
possibile. Ebbene in questo caso si modifica un membro private
, contravvenendo alle
regole della OOP (abbreviazione di "programmazione orientata agli oggetti").
Un buon compilatore dovrebbe impedirlo, o almeno tirar fuori un avvertimento,
ma con DJGPP e Visual C++ non ho ottenuto niente! Probabilmente i programnmatori dei
compilatori si sono scordati di tener conto del problema!
In alcuni casi potresti volere che solo i figli di una classe abbiano il permesso di
accedere ad alcuni suoi membri. Quindi, non ci sarà modo di accedere a quei membri
dall'esterno, ma all'interno sia della classe padre che figlia, ogni modifica sarà
permessa. Questo tipo di dichiarazioni sono dette protette e precedute dalla keyword protected
.
Ndt: notare la differenza con le dichiarazioni private
: esse non sono accessibili nè
all'esterno della classe, nè dai suoi discendenti.
class father { protected: // provate ad commentarla int a; }; class A : public father { int GetA() { return a; } // può accedere ad a }; ... { father f; int x = f.a; // rifiutato dal compilatore }In altre parole, con questo tipo di ereditarietà, cioè l'ereditarietà pubblica, le classi figlie hanno gli stessi diritti di accesso ai membri
public
e private
degli utilizzatori esterni: i public
sono manipolabili direttamente
dalle classi figlie, i private
no.
Invece i membri protected
sono speciali: le classi figlie possono usarli
direttamente, ma non il resto del mondo (cioè altre classi non figlie, normali funzioni, il main).
Tuttavia ci sono altri tipi di ereditarietà, il cui scopo è cambiare alcuni
specificatori di accesso per i membri ereditati; la cosa più comune è far
sì che membri pubblici siano ereditati come protetti o privati, anzichè
come pubblici. Continuate a leggere...
public
tra ":" e il nome della classe padre:
class father { public: int a; }; class A : public father { int GetA() { return a; } // può accedere ad a } ... { A a_obj; x = a_obj.a; // si possono accedere membri pubblici ereditati come tali }Una classe figlia può anche proteggere i membri che ha ereditato da suo padre, facendo sì che le proprietà ereditate da suo padre siano "interne". La ereditarietà deve essere dichiarata di tipo protetto (tutti i membri pubblici della classe padre diventeranno protetti) o di tipo privato (se invece si vuole che tutti i membri pubblici diventino privati). La differenza tra derivazione protetta e privata si manifesta solo nei figli dei figli della classe padre, poichè per il resto del mondo (main, normali funzioni o altri classi che usano la classe figlia e nipote) i campi pubblici della classe padre ereditati come protected o private sono in entrambi i casi inaccessibili.
class father { public: int a; }; class A : private father { int GetA() { return a; } // a si può accedere qui (a è diventato un membro privato) } ... { A a_obj; x = a_obj.a; // rifiutato dal compilatore: non si può accedere al membro privato a }Nota che puoi omettere la keyword
public
, protected
o private
. In tal caso si intende private
per default.
La tabella seguente riassume come cambiano i livelli di accesso in una classe figlia a seconda dei 3 tipi di derivazione possibili:
classe Padre | classe Figlia | ||
---|---|---|---|
derivazione public |
derivazione protected |
derivazione private |
|
public |
public |
protected |
private |
protected |
protected |
protected |
private |
private |
non accessibili |
non accessibili |
non accessibili |
La sintassi è proprio la stessa:
class Father { int a; public: Father (int aa) { a = aa; } }; class Child : public Father { int b; public: Child (int aa) : Father (aa) {} // Ecco un costruttore inline Child (int, int); // ed uno che è una normale funzione }; Child::Child (int aa, int bb) : Father (aa) { b = bb; } /* alternativa: Child::Child (int aa, int bb) : Father (aa), b(bb) { }*/Provate ad omettere la parte di linea
: Father (aa)
nella definizione del costruttore Child (int aa)
.
Cosa succede? abbiamo già fatto vedere una situazione
simile qui.
class Father { public: void MakeAThing(); }; class Child : public Father { public: void MakeAThing(); }; ... { Father father; father.MakeAThing(); // metodo MakeAThing di Father chiamato Child child; child.MakeAThing(); // metodo MakeAThing di Child chiamato }Nella nuova versione della funzione, potresti aver bisogno di chiamare la versione della funzione della classe padre, o persino una funzione globale con lo stesso nome. Come fare? Una chiamata a MakeAThing() della classe Child all'interno di MakeAThing() della classe Child stessa è una chiamata ricorsiva e non corrisponde a nessuna delle due chiamate precedenti. La cosa è possibile specificando più informazioni al compilatore all'atto della chiamata, usando :: e il nome della classe a cui appartiene la funzione che vuoi chiamare. Ecco un esempio che chiarisce tutto:
void MakeAThing(); class Father { public: void MakeAThing(); }; class Child : public Father { public: void MakeAThing() { Father::MakeAThing(); // viene chiamato il metodo di Father ::MakeAThing(); // viene chiamata la funzione globale //MakeAThing(); // questa è una chiamata ricorsiva! } }; ... { Father father; father.MakeAThing(); // metodo MakeAThing di Father chiamato Child child; child.MakeAThing(); // metodo MakeAThing di Child chiamato MakeAThing(); // chiamata una funzione globale }
class Father { ... }; class Child : public Father { ... }; ... void ExampleFunction (Father &); ... { Father father; ExampleFunction (father); // Normale chiamata Child child; ExampleFunction (child); // un oggetto child è considerato come uno di tipo father }Questo proprietà vale per gli oggetti, i puntatori agli oggetti e i riferimenti agli oggetti. Così definendo una classe figlia di una classe, puoi apportare dei miglioramenti rispetto a quest'ultima e nello stesso tempo essere in grado di usarne tutte le caratteristiche e le funzionalità. Questo può essere fatto anche se non hai il codice sorgente della classe padre! È questa la ragione principale del successo della programmazione orientata agli oggetti.