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.