Elementi di programmazione ad oggetti a. a. 2009/2010 Corso di Laurea Magistrale in Ingegneria Elettronica Docente: Mauro Mazzieri, Dipartimento di Ingegneria Informatica, Gestionale e dellAutomazione
Lezione 6 Ereditarietà. Funzioni virtuali e polimorfismo.
Ereditarietà Processo mediante il quale un oggetto acquisisce le proprietà di un altro oggetto Concetto di classificazione gerarchica Una classe eredita attributi e metodi da una classe madre La classe base è detta superclasse La classe derivata è detta sottoclasse La sottoclasse può aggiungere, occultare o ridefinire lo stato e i metodi della superclasse Grazie allereditarietà, la sottoclasse deve definire solo quelle caratteristiche la rendono unica Un oggetto della sottoclasse rappresenta un esempio più specifico di un oggetto della classe più generale
Esempio: classe base Definisco una classe che rappresenta un impiegato: class Impiegato { string nome; int salario; } Successivamente, voglio definire la classe dei manager
Esempio: classe derivata class Manager { // dati dellimpiegato string nome; int salario; // nuovi dati int livello; } class Manager { // contiene i dati dellimpiegato Impiegato i; int livello; } class Manager : public Impiegato { int livello; };
Ereditarietà: diagramma UML Relazione is-a Freccia verso la classe base È la classe derivata che fa riferimento alla classe base, non viceversa
Principio di sostituzione La classe derivata è un sottotipo della classe base Posso usare un oggetto della classe derivata al posto di un oggetto della classe base Non vale il contrario Principio di sostituzione di Liskov: Un oggetto di una classe derivata può essere ovunque si usi un oggetto della classe di base
Classi derivate: membri Un membro di una classe derivata può utilizzare membri pubblici della classe base I membri pubblici definiti per la classe base sono implicitamente definiti (ereditati) dalla classe base La classe derivata può definire nuovi membri
Classi derivate: membri privati e protetti Una classe derivata non può usare i membri privati della classe base … sarebbe possibile accedere alla parte privata definendo una sottoclasse Oltre alla parte public e private, nella dichiarazione di una classe può essere presente una parte protected I membri protetti sono come membri privati, salvo che per le classi derivate
Classi derivate: costruttori I costruttori non si ereditano Vanno ridefiniti nelle classi derivate Un oggetto base deve esistere prima che possa diventare un oggetto derivato… … dunque il costruttore della classe base deve essere chiamato prima che il costruttore della classe derivata Manager::Manager(const string& n, int s, int l) : Impiegato(n, s), livello(l) { } Lista di inizializzazione
Classi derivate: distruttori Linvocazione di un distruttore di una classe derivata produce automaticamente una chiamata a tutti i distruttori delle classi base I distruttori vengono richamati risalendo nella gerarchia delle classi Il distruttore di una classe derivata neon deve chiamare il costruttore della classe base, ma deve occuparsi delle risorse aggiuntive impegnate dalla classe derivata
Esempio: costruttori e distruttori #include using namespace std; class base { public: base() { cout << "Costruzione di base\n"; } ~base() { cout << "Distruzione di base\n"; } }; class derived1 : public base { public: derived1() { cout << "Costruzione di derived1\n"; } ~derived1() { cout << "Distruzione di derived1\n"; } }; class derived2 : public derived1 { public: derived2() { cout << "Costruzione di derived2\n"; } ~derived2() { cout << "Distruzione di derived2\n"; } };
Esempio: costruttori e distruttori main() { derived2 ob; return 0; } Output: Costruzione di base Costruzione di derived1 Costruzione di derived2 Distruzione di derived2 Distruzione di derived1 Distruzione di base I costruttori vengono chiamati in ordine di derivazione, i distruttori in ordine inverso.
Visibilità della derivazione Derivazione pubblica Dovrebbe valere il principio di sostituzione di Liskov I membri pubblici e protetti della classe base rimangono tali nella classe derivata Posso usare la classe derivata al posto della classe base Derivazione protetta I membri pubblici della classe base vengono resi protetti nella classe derivata Derivazione privata I membri pubblici e protetti della classe base vengono resi privati nella classe derivata La derivazione non pubblica non è una relazione is- a, ma un dettagli implementativo (sostanzialmente equivalente alla composizione, salvo che per la ridefinizione dei membri)
Livelli di accesso nella classe derivata Classe base Classe derivata public Classe derivata protected Classe derivata private Membro pubblico publicprotectedprivate Membro protetto protected private Membro privato Non visibile
Ridefinizione dei membri Ridefinire un membro della classe base nella classe derivata permette di associare un diverso dato o metodo allo nome già utilizzato nella classe base Vale anche per la ridefinizione dei membri il principio di Liskov Loutput di un membro è vincolato a rispettare gli stessi requisiti della classe base I prerequisiti richiesti alla classe base sono almeno altrettanto vincolanti di quelli su una classe derivata
Ereditarietà multipla Lereditarietà multipla consente ad una classe di essere derivata da più classi base In presenza di ereditarietà multipla un oggetto della classe derivata eredita tutti i membri delle classi di base
Risoluzione delle ambiguità Un membro della classe derivata nasconde tutti i membri con lo stesso nome delle classi base Se più classi base hanno membri con lo stesso nome, si genera unambiguità che causa un errore di compilazione Occorre risolvere lambiguità con loperatore di scope :: class date { // … public: string show(); } class time { // … public: string show(); } class datetime : public date, public time { // … } datetime dt; Dt.date::show();
Polimorfismo Uninterfaccia, molti metodi Si può creare uninterfaccia generica per un gruppo di attività collegate Il polimorfismo è il meccanismo che permette di usare una classe derivata al posto di una classe base Uno stesso metodo ha effetti differenti a seconda del tipo delloggetto su cui è chiamata Le tecniche che consentono il polimorfismo sono il binding dinamico e le funzioni virtuali
Polimorfismo Un riferimento o puntatore ad un oggetto è polimorfo se può essere riferito a runtime ad oggetti diversi Il tipo statico è la classe dichiarata del riferimento Il tipo dinamico è la classe delloggetto dinamicamente riferito Non si può avere comportamento polimorfo quando un tipo è manipolato direttamente La chiamata di un metodo attraverso un riferimento polimorfico comporta la decisione a run-time di quale metodo chiamare (binding dinamico) Il compilatore alloca (solo per gli oggetti polimorfi) uno spazio per memorizzare il tipo dinamico delloggetto
Binding dinamico Il binding è il collegamento della chiamata di una funzione al corpo della funzione chiamata Early binding Il collegamento viene effettuato dal compilatore o dal linker Late binding Il collegamento viene eseguito dinamicamente a runtime Il linguaggio permette di conoscere il tipo dinamico delloggetto e di chiamare la funzione membro appropriata
Riepilogo delle relazioni tra oggetti Ad un puntatore alla classe base è possibile assegnare oggetti della classe base o di classi derivate Se i membri sono virtuali, viene usato il membro del tipo dinamico corrispondente Non è possibile accedere ai membri delle classi derivate Un puntatore alla classe base può accedere ai metodi della classe derivata solo operando esplicitamente un cast (downcasting) Ad un puntatore ad una classe derivata non è possibile assegnare lindirizzo di un oggetto della classe base Un oggetto derivato è un oggetto base, ma un oggetto base non è un oggetto derivato
Funzioni virtuali Una funzione virtuale di una classe base può essere ridefinita dalle classi derivate Class Impiegato { public: virtual void saluta() const { cout << ciao; } } class Manager { public: void saluta() const { cout << Buongiorno; } } Luso della parola chiave virtual nella dichiarazione attiva il late binding del metodo Il late binding avviene solo quando la classe base ha delle funzioni virtuali Le funzioni virtuali devono essere definite anche nella classe base
Funzioni virtuali Una funzione dichiarata virtuale nella classe base è virtuale per tutte le classi derivate Una classe che contiene almeno una funzione virtuale è polimorfa Loperazione consistente nel ridefinire il metodo virtuale nella classe derivata è detta overriding Le funzioni nelle classi derivate con lo stesso prototipo della funzione virtuale base verranno chiamate attraverso il late binding Vale comunque lereditarietà per le funzioni virtuali, dunque se non vengono ridefinite nella classe derivata viene chiamato il metodo della classe base
Puntatore alla classe base e oggetto della classe derivata Se un puntatore al tipo della classe base viene usato per accedere ad un oggetto della classe derivata Se non si tratta di metodi virtuali, viene chiamato il membro della classe base La decisione è compiuta staticamente sulla base del tipo del puntatore La funzione accede ai membri della classe base Se si tratta di funzioni virtuali si accede ai membri della classe derivata È il tipo delloggetto ha determinare a quali membri accedere
Funzioni virtuali pure Una classe si può scrivere anche solo per consentire di derivarne altre classi, anche se non sarà mai invocata Una funzione virtuale pura è una funzione virtuale che non ha definizione nella sua classe base Definire il prototipo della funzione senza scriverne il corpo La funzione virtuale pura viene definita scrivendo =0 al posto del corpo Non si può istanziare una classe contente funzioni virtuali pure! La definizione di una funzione virtuale pura costringe tutte le derivate (se vogliono poter essere istanziate) a definire la propria versione
Classi astratte Una classe è astratta se non fornisce le implementazioni di tutti i propri membri Una classe astratta contiene almeno una funzione virtuale pura Una classe astratta non può essere istanziata Le classi astratte servono come punto di partenza per la definizione di altre classi Interfacce Sottoinsieme comune di comportamenti
Distruttori virtuali Se si allocano dinamicamente oggetti e si deallocano con delete, e si sta usando un puntatore alla classe base per riferirsi ad un oggetto della classe derivata Viene chiamato il distruttore della classe base… Occorre rendere virtual il distruttore Classi polimorfe solitamente hanno distruttori virtuali. Un costruttore non può essere virtuale!