Ereditarietà
Da Hacknowledge.
L'ereditarietà è uno dei meccanismi alla base della programmazione a oggetti, nonché uno di quelli che ha contribuito alla versatilità, alla modularità logica e all'alto livello di astrazione di questo approccio. Il meccanismo è semplice: io ho una classe base, poi tante classi che hanno molto in comune con la classe base e in più aggiungono qualcosa di loro. Io allora definisco la classe base, faccio ereditare i suoi costrutti alle classi figlie e in più definisco poi ciò che voglio nelle classi figlie. Esempio pratico, immaginiamo di dover gestire un software per il noleggio di auto, e quindi dover manipolare nel nostro codice degli oggetti di tipo Automobile. È immediato che tutte le automobili hanno delle caratteristiche in comune (avranno una certa cilindrata, una certa potenza, un certo numero di chilometri percorsi, saranno di una certa marca e un certo modello ecc.), ma poi è necessario fare una specializzazione. Ad esempio posso definire una classe AutoSportiva, che contenga anche la velocità massima raggiungibile, la classe Autovan che contenga anche il numero di posti e la categoria minima di patente richiesta per la guida, la classe AutoRipara, che classifichi un'automobile guasta e che contenga il tipo di guasto, il nome dell'utente sotto il quale è avvenuto il guasto, il nome dell'autofficina addetta alla riparazione ecc.
[modifica] Ereditarietà in C++
La sintassi per esprimere un'ereditarietà in C++ è la seguente:
class A { ... }; class B : <public|protected|private> A { ... };
In questo modo dico al compilatore che la classe B dovrà ereditare i membri della classe A, e rendere i membri ereditati pubblici/protetti/privati a seconda dell'identificatore che associo. Attenzione: una classe figlia può ereditare solo i membri pubblici e protetti della classe padre, mentre invece i membri privati rimarranno visibili solo alla classe padre. Vediamo subito un esempio pratico tornando all'esempio di sopra dell'automobile:
#include <iostream> #include <string> using namespace std; class Automobile { public: typedef enum { nomarca, Fiat, Alfa, Ferrari, Citroen, Peugeot, Ford, Toyota, ... } marca; typedef enum { noalim, benzina, diesel, metano } alimentazione; protected: int id_auto; marca m; string modello; alimentazione a; float km; int cilindrata,hp; public: marca getAuto(string *mod) { mod = new string(modello); return m; } float getKm() { return km; } int getCilindrata() { return cilindrata; } int getHp() { return hp; } Automobile() { m=nomarca; a=noalim; km=0.0; cilindrata=0; hp=0; } Automobile (marca mm, string mod) { m=mm; modello=mod; } ... }; // Classe AutoSport che eredita tutti i membri protetti e pubblici della classe Automobile class AutoSport : public Automobile { int max_speed; public: // Costruttore vuoto della classe. Richiama il costruttore vuoto della classe automobile // e in più inizializza a 0 il parametro max_speed AutoSport() : Automobile() { max_speed=0; } AutoSport (marca mm, string mod, int sp) : Automobile (mm,mod) { max_speed=sp; } int getMaxSpeed() { return max_speed; } }; main() { AutoSport a(AutoSport::Alfa,"147",200); cout << "Massima velocità: " << a.getMaxSpeed() << endl; cout << "Cilindrata: " << a.getCilindrata() << endl; }
Ho quindi dichiarato la classe AutoSport che eredita tutti i membri della classe automobile e in più definisce un nuovo parametro privato (max_speed) e un nuovo metodo per accedervi. Si noti anche che i costruttori non fanno altro che richiamare i costruttori della classe padre. Dopo che il costruttore della classe padre è stato eseguito, il costruttore, in più, provvede anche a inizializzare il parametro aggiuntivo max_speed. Si noti la sintassi attraverso la quale è possibile richiamare il costruttore della classe padre:
NomeClasse (lista argomenti) : ClassePadre (eventuali argomenti)
[modifica] Ereditarietà multipla
Una classe può anche ereditare membri da più di una classe, effettuando quindi un'ereditarietà multipla. Tornando al caso di prima, immaginiamo di voler gestire il noleggio di un'automobile come un oggetto a parte, che però estenderà due classi diverse: la classe Automobile e la classe Cliente, importando i membri di entrambe:
class Cliente { protected: string nome; string cognome; string CF; public: string getNome() { return nome; } string getCognome() { return cognome; } }; class Noleggio : public Automobile, public Utente { time_t data; public: Noleggio (string codfisc, int a, time_t t) { CF=codfisc; id_auto=a; data=t; } time_t getData() { return data; } }; main() { Noleggio n("ABCDEF",1,time((unsigned) NULL)); cout << "Auto affittata da " << n.getNome() << " " << n.getCognome() << " in data " << n.getData() << endl; }
Vediamo quindi che è possibile definire una classe che estenda più di una sola classe, ovvero erediti membri da più fonti.
[modifica] Controindicazioni
Tuttavia, l'ereditarietà multipla va usata con cautela, in quanto può portare effetti collaterali difficili da gestire. Si consideri ad esempio il seguente codice:
#include <iostream> using namespace std; class A { int n; public: int foo() { return n; } A(int nn) { n=nn; } }; class B { int n; public: int foo() { return ++n; } }; class C : public A, public B { public: C(int nn) : A(nn) {}; }; int main() { C c(3); cout << c.foo() << endl; }
La funzione foo() è definita in entrambe le classi ereditate da C. Domanda: quale delle due funzioni ereditate verrà richiamata? Risposta: un buon compilatore darà un risultato del genere:
error: request for member ‘foo’ is ambiguous
proprio perché ereditando due membri con lo stesso nome non saprà quale richiamare. Altri compilatori più vecchi portrebbero invece non gestire questo caso, portando a risultati imprevedibili. È per questo che il meccanismo dell'ereditarietà multipla va gestito con cautela, specie quando le classi da cui si eredita hanno membri in comune, ed è anche per questo che linguaggi a oggetti moderni come Java hanno preferito non implementare questo approccio, facendo in modo che una classe può ereditare al massimo una ed una sola classe.

