Eredità in C++ Corso di Linguaggi di Programmazione ad Oggetti 1. a cura di Giancarlo Cherchi



Documenti analoghi
Modulo 4: Ereditarietà, interfacce e clonazione

Corso di Programmazione ad Oggetti

Soluzione dell esercizio del 2 Febbraio 2004

Programmazione a Oggetti Lezione 10. Ereditarieta

Progettazione : Design Pattern Creazionali

Informatica 3. LEZIONE 7: Fondamenti di programmazione orientata agli oggetti (1)

Relazioni tra oggetti e classi : Composizione. Relazioni tra oggetti e classi : esempio di Aggregazione. classe contenitore

void funzioneprova() { int x=2; cout<<"dentro la funzione x="<<x<<endl; }

Visibilità dei Membri di una Classe

Parola chiave extends


Inizializzazione, Assegnamento e Distruzione di Classi

Manuale Amministratore Legalmail Enterprise. Manuale ad uso degli Amministratori del Servizio Legalmail Enterprise

Programmazione a Oggetti Modulo B

Database. Si ringrazia Marco Bertini per le slides

Introduzione. Java. Composizione. Esempio -- composizione. G. Prencipe È qualcosa che abbiamo già visto varie volte

Introduzione alla programmazione in C

Generalizzazione di funzioni e di classi. Macro come funzioni generiche

Esercitazione n 4. Obiettivi

Tipi primitivi. Ad esempio, il codice seguente dichiara una variabile di tipo intero, le assegna il valore 5 e stampa a schermo il suo contenuto:

Concetto di Funzione e Procedura METODI in Java

12 - Introduzione alla Programmazione Orientata agli Oggetti (Object Oriented Programming OOP)

!"#$%&&'()#*%+%+!"#$"',,'()#*%+ -")%*&'&'+'$.)+-$$%&&) !"#$%&&'(%)'*+%",#-%"#.'%&'#/0)-+#12"+3,)4+56#7+#.')8'9

costruttori e distruttori

Traccia di soluzione dell esercizio del 25/1/2005

Corso di Informatica

Object Oriented Programming

Sistemi Operativi MECCANISMI E POLITICHE DI PROTEZIONE. D. Talia - UNICAL. Sistemi Operativi 13.1

MECCANISMI E POLITICHE DI PROTEZIONE 13.1

Variabili e tipi di dato

Funzioni in C. Violetta Lonati

Lezione 4. Modello EER

La struttura dati ad albero binario

Esercitazione di Basi di Dati

Gli array. Gli array. Gli array. Classi di memorizzazione per array. Inizializzazione esplicita degli array. Array e puntatori

Corso di Informatica

I casi d uso corrispondono ai compiti che l attore (che può essere una persona fisica e non) può svolgere.

Programmazione a Oggetti e JAVA. Prof. B.Buttarazzi A.A. 2012/2013

Fondamenti di Informatica 1. Prof. B.Buttarazzi A.A. 2010/2011

Organizzazione degli archivi

Gestione Rapporti (Calcolo Aree)

Banca dati Professioniste in rete per le P.A. Guida all uso per le Professioniste

15 - Packages. Programmazione e analisi di dati Modulo A: Programmazione in Java. Paolo Milazzo

Creare una nuova spedizione personalizzata.

Oggetti Lezione 3. aspetti generali e definizione di classi I

Protezione. Protezione. Protezione. Obiettivi della protezione

GESTIONE DEI PROCESSI

Laboratorio di programmazione

LA GESTIONE DELLE VISITE CLIENTI VIA WEB

Linguaggi Corso M-Z - Laurea in Ingegneria Informatica A.A Esercitazione. Programmazione Object Oriented in Java

Gli attributi di STUDENTE saranno: Matricola (chiave primaria), Cognome, Nome.

Regione Toscana. ARPA Fonte Dati. Manuale Amministratore. L. Folchi (TAI) Redatto da

Gestione Risorse Umane Web

14 - Packages. Programmazione e analisi di dati Modulo A: Programmazione in Java. Paolo Milazzo

dall argomento argomento della malloc()

NOTE OPERATIVE. Prodotto Inaz Download Manager. Release 1.3.0

Strutture. Strutture e Unioni. Definizione di strutture (2) Definizione di strutture (1)

Object Oriented Software Design

Progettazione di Basi di Dati

CRM Configurazione e gestione accessi

LINGUAGGI DI PROGRAMMAZIONE

I database relazionali (Access)

Sistema operativo: Gestione della memoria

UML Diagrammi delle classi. UML Diagramma classi 1

Introduzione alla teoria dei database relazionali. Come progettare un database

Comunità on Line Manuale Utente. Fascicolo 1. Come iscriversi a Comunità On Line. Versione 1.1

Università degli Studi di Cassino Corso di Fondamenti di Informatica Puntatori. Anno Accademico 2010/2011 Francesco Tortorella

Mon Ami 3000 Lotti e matricole Gestione della tracciabilità tramite lotti/matricole

Indice generale. OOA Analisi Orientata agli Oggetti. Introduzione. Analisi

Programmazione Java: Variabili membro, Metodi La parola chiave final

Java: Compilatore e Interprete

Procedura SMS. Manuale Utente

MANUALE PARCELLA FACILE PLUS INDICE

Uso di JUnit. Fondamenti di informatica Oggetti e Java. JUnit. Luca Cabibbo. ottobre 2012

RIFERIMENTI ATTORI GLOSSARIO. ERRORI COMUNI REV. REQUISITI INGEGNERIA DEL SOFTWARE Università degli Studi di Padova

Realizzazione di una classe con un associazione

Mon Ami 3000 Centri di costo Contabilità analitica per centri di costo/ricavo e sub-attività

Il database management system Access

Linguaggi e Paradigmi di Programmazione

Manuale d uso Software di parcellazione per commercialisti Ver [05/01/2015]

Mon Ami 3000 Varianti articolo Gestione di varianti articoli

CAPACITÀ DI PROCESSO (PROCESS CAPABILITY)

Introduzione a ROOT. 1. Informazioni generali

BASI DI DATI - : I modelli di database

Telerilevamento e GIS Prof. Ing. Giuseppe Mussumeci

1) GESTIONE DELLE POSTAZIONI REMOTE

Amministrazione gruppi (Comunità)

Figura 1 Le Icone dei file di Excel con e senza macro.

VPN CIRCUITI VIRTUALI

CREAZIONE DI UN AZIENDA

FIRESHOP.NET. Gestione Lotti & Matricole.

Corso di Fondamenti di Informatica

Come modificare la propria Home Page e gli elementi correlati

Definizione di classi con array di oggetti

Workland CRM. Workland CRM Rel /11/2013. Attività --> FIX. Magazzino --> NEW. Nessuna --> FIX. Ordini --> FIX

N.B. nelle schermate esemplificative che seguiranno i dati personali sono stati oscurati.

Introduzione a Classi e Oggetti

SISTEMA CONTROLLO ACCESSO PARCHI AVVENTURA JUNGLE TRACK

MANUALEDIUTILIZZO MODULO CRM POSTVENDITA

Fondamenti di Informatica e Laboratorio T-AB T-16 Progetti su più file. Funzioni come parametro. Parametri del main

Transcript:

Eredità in C++ Corso di Linguaggi di Programmazione ad Oggetti 1 a cura di Giancarlo Cherchi 1

Introduzione Il meccanismo dell eredità consente di sfruttare delle relazioni tipo/sottotipo, ereditando attributi e/o metodi in modo da evitare di reimplementare caratteristiche condivise In C++ questo è possibile grazie alla derivazione di classe: la classe da cui si eredita è detta classe base; la nuova classe è detta classe derivata; l insieme delle istanze di classi base e derivate è denominata gerarchia di ereditarietà di classi. 2

Introduzione Ereditare da una libreria di classi già esistente consente il riuso del codice con un meccanismo che va ben oltre la copia e modifica di quanto scritto precedentemente. Si creano nuove classi, non partendo da zero, ma sfruttando ed estendendo altre classi che qualcuno ha creato e collaudato 3

Definire una gerarchia di classi Supponiamo di voler costruire un programma che gestisce i dipendenti di un azienda. Potremmo avere strutture dati del tipo: struct Dipendente { char *nome, *cognome; int dipartimento; Data data_assunzione; /* */ }; struct Manager { Dipendente dip; Dipendente * gruppo[]; // persone gestite int livello; /* */ }; 4

Definire una gerarchia di classi Un Manager è un Dipendente L affermazione può sembrare ovvia per un umano ma, lasciando le strutture indipendenti, il compilatore non è in grado di capire questa relazione In particolare, non è possibile inserire un Manager (o un suo puntatore) in una lista di Dipendenti Si potrebbe effettuare una conversione esplicita da Manager* a Dipendente* o inserire un puntatore al campo dip in una lista di Dipendenti, ma la soluzione è confusa e inelegante. 5

Definire una gerarchia di classi Cambiando la definizione di Manager, mediante l aggiunta di un po di informazione, è possibile informare il compilatore della dipendenza desiderata: struct Manager : public Dipendente { Dipendente * gruppo[]; int livello; /* */ }; Manager è derivato da Dipendente e possiede i membri della sua classe base oltre ai suoi membri proprietari. 6

Definire una gerarchia di classi La derivazione si rappresenta comunemente con una freccia che va dalla classe derivata verso la classe base: Dipendente Manager Talvolta la classe base è chiamata superclasse e quella derivata sottoclasse; Tuttavia, la classe derivata è più grande della classe base poiché contiene più dati e fornisce più funzionalità 7

Definire una gerarchia di classi L operazione di derivazione illustrata rende Manager un sottotipo di Dipendente: un Manager può essere utilizzato ogni qualvolta si può utilizzare un Dipendente. Esempio: Dipendente * dip[10]; Manager m1, m2; Dipendente d1, d2, d3; dip[0] = &d1; dip[1] = &m1; // corretto! dip[2] = &d3; 8

Definire una gerarchia di classi Quindi: un Manager è anche un Dipendente e un Manager* può essere usato dove è richiesto un Dipendente*. NON è vero il viceversa! Un Manager è un caso particolare di Dipendente e usare Dipendente* al posto di Manager* richiede una conversione esplicita: Dipendente* pdip = &m1; // ok: man. è dip. Manager* pman = &d1; // errore: dip. non man. pman->livello = 2; // disastro! d1 non ha livello pman = (Manager*)pdip; // conv. Esplicita pman->livello = 2; // ok: man ha un livello 9

Definire una gerarchia di classi Generalizzando: se una classe Derivata ha Base come classe base, il puntatore Derivata* può essere assegnato a un puntatore di tipo Base* senza conversione esplicita: Derivata * pd; Base * pb = pd; l assegnamento opposto, da Base* a Derivata* richiede una conversione esplicita (tramite cast) Base * pb; Derivata * pd = static_cast<derivata *>(pb); 10

Definire una gerarchia di classi NOTA: una classe deve essere definita prima di poter essere utilizzata come base: class Dipendente; class Manager: public Dipendente { // errore!! }; Una classe derivata può diventare a sua volta una classe base per un altra classe. Grazie anche all eredità multipla, il C++ consente di definire gerarchie di classe rappresentabili con grafi aciclici diretti 11

Accesso ai membri Una classe derivata può accedere ai membri pubblici (e protetti) di una classe base ma non ai membri privati: class Dipendente { char *nome, *cognome; public: void stampa() const; char * nome() const; /* */ }; class Manager: public Dipendente { /* */ public: void stampa() const; }; 12

Accesso ai membri Esempio di codice corretto: void Manager::stampa() { cout << Il nome è << nome() << endl; } Esempio di codice errato: void Manager::stampa() { cout << cognome: << cognome << endl; } 13

Accesso ai membri Una soluzione tipica è usare i membri pubblici della classe base: void Manager::stampa() { Dipendente::stampa(); cout << livello; }; NOTA: riuso la stampa definita nella classe base e lo indico con Dipendente:: Scrivere soltanto stampa() significherebbe una chiamata ricorsiva (la ridefinizione del metodo in Manager maschera quella di Dipendente)! 14

Accesso ai membri Attenzione all overloading! Infatti, dichiarare: class Manager: public Dipendente { void stampa(ostream & s); // }; la stampa di Manager nasconde comunque la stampa di Dipendente, nonostante abbia una signature diversa! 15

Accesso ai membri La soluzione può essere: class Manager: public Dipendente { public: void stampa() { Dipendente::stampa(); } // oppure: using Dipendente::stampa; }; cioè: creare una semplice inline che richiama la funzione della base oppure importare il simbolo dalla classe base (specificandone però solo il nome senza parametri). 16

Accesso ai membri In una progettazione Object-Oriented tipicamente vi è un fornitore (chi progetta e implementa la classe) e gli utenti (che utilizzano l interfaccia pubblica del fornitore) Il fornitore decide cosa rendere visibile all esterno e cosa nascondere; La classe viene perciò divisa in due livelli di accesso: public e private 17

Accesso ai membri Con l eredità possono esserci vari fornitori di classi; ad esempio un fornitore di classe base e altri fornitori di classi da questa derivate Il fornitore di un sottotipo può avere la necessità di accedere all implementazione della classe base Per rendere possibile questa operazione, pur continuando ad impedire l accesso generale all implementazione, il C++ fornisce un ulteriore livello d accesso: protected 18

Livello d accesso protected Se un membro è dichiarato protected, il suo nome può essere utilizzato soltanto dalle funzioni membro della classe in cui è stato dichiarato (analogamente a private) o nelle funzioni membro delle classi da questa derivate NOTA IMPORTANTE: le classi derivate NON hanno accesso alla sezione private della classe base 19

Costruttori e loro attivazione Le classi derivate possono aver bisogno di uno o più costruttori Se la classe base ha dei costruttori, un costruttore deve essere invocato dalla classe derivata Possono essere invocati implicitamente soltanto i costruttori di default Se tutti i costruttori della classe base richiedono argomenti, uno di questi deve essere esplicitamente invocato. 20

Costruttori e loro attivazione Consideriamo ad esempio: class Dipendente { char * nome; public: Dipendente(const char * n); }; class Manager: public Dipendente { public: Manager(const char *n, int liv); }; 21

Costruttori e loro attivazione Gli argomenti del costruttore della classe base saranno specificati nella definizione del costruttore della classe derivata: Dipendente::Dipendente(const char * n) { // } Manager::Manager(const char *n, int liv) : Dipendente (n), livello (liv) { // } 22

Costruttori e loro attivazione Un costruttore di una classe derivata può specificare come inizializzare i suoi membri e richiamare un costruttore della base immediatamente superiore Non può inizializzare direttamente i membri della classe base, nemmeno se dichiarati protetti (questa operazione deve essere fatta mediante nel costruttore della classe base) Gli oggetti sono costruiti in ordine: prima il costruttore della base, poi i membri della classe derivata (nell ordine in cui sono stati definiti nella classe e non nell ordine in cui sono elencati) 23

Costruttori e loro attivazione Esempio: class B { int eta; public: B(int e) : eta (e) { } }; class D: public B { float peso; float altezza; public: D(int d, float p, float a) }; 24

Costruttori e loro attivazione D::D(int d, float p, float a) : altezza(a), peso(p), B(d) { // inizializzazioni varie di D } L ordine sarà: costruttore di B col parametro intero d inizializzazione del membro peso al valore p inizializzazione di altezza al valore a parte restante del costruttore di D 25

Costruttori e loro attivazione L unico caso in cui si può richiamare implicitamente il costruttore di default è quando la classe base NON ha costruttori: class B { protected: int n; }; class D : public B { float f; public: D (float v) : f (v) { } // B() è richiamato implic. }; 26

Distruttori In generale, l ordine di invocazione dei distruttori per un oggetto di una classe derivata è il contrario dell invocazione dell ordine dei costruttori Se non è stato definito dall utente, il compilatore utilizza un distruttore di default Attenzione a quando si distrugge in una classe derivata un oggetto tramite un puntatore della classe base! 27

Distruttori Manager::~Manager() { delete pdip1; delete pman1; delete pdip2; } se pdip1 punta a un Manager (cosa possibile), occorre chiamare il distruttore opportuno (di Manager) e non quello della classe base! Questo problema si risolve dichiarando i distruttori come virtuali. 28

Copia e assegnamento E possibile assegnare un istanza di una sottoclasse ad un istanza della sua classe base o richiamare il costruttore di copia della base con un oggetto della classe derivata: Manager m1, m2; Dipendente d = m1; // Costruttore di copia d = m2; // Assegnamento L operazione è lecita ma l operatore = e il costruttore di copia di Dipendente non conoscono i dettagli di Manager! 29

Copia e assegnamento Cosa accade? Viene copiata (o costruita) soltanto la parte Dipendente di Manager, ovvero la parte condivisa tra classe base e derivata. Questo fenomeno prende il nome di slicing ed è spesso fonte di sorprese. Per questo motivo (oltre che per ragioni di efficienza) è preferibile l uso di puntatori e/o referenze. NOTA: Gli operatori di assegnamento NON sono ereditati, così come i costruttori. 30

Funzioni Virtuali Nell utilizzo delle gerarchie, ci si scontra con questo problema: Dato un puntatore Base*, a quale tipo (tra i derivati da Base) corrisponde effettivamente l oggetto puntato? Esistono fondamentalmente 4 soluzioni: Assicurarsi di puntare ad oggetti di un solo tipo Inserire nella classe base un campo identificativo del tipo puntato Usare dynamic_cast Usare le funzioni virtuali 31

Funzioni Virtuali Il primo caso è poco flessibile, e costringe all uso di liste omogenee Il secondo caso è funzionale ma ha come punto debole l impossibilità di controllo dei tipi da parte del compilatore ed è difficilmente applicabile (ed è difficile da aggiornare) nel caso di gerarchie con molti tipi Il terzo caso è una variante del secondo supportata dal linguaggio L ultimo è una potente variante del secondo, ma stavolta type-safe e gestita dal linguaggio. 32

Funzioni Virtuali Esempio del secondo metodo: class Dipendente { public: enum TipoDip { M, D }; TipoDip tipo; Dipendente() : tipo (D) { } }; char nome[30]; class Manager: public Dipendente { int level; public: Manager() : tipo(m) { } }; 33

Funzioni Virtuali Si potrebbe avere una funzione per la stampa: void stampa_dip (const Dipendente * pd) { switch (pd->tipo) { } case Dipendente::D: cout << nome: << pd->nome << endl; break; case Dipendente::M: cout << nome: << pd->nome << endl; const Manager * pm = static_cast<manager*>(pd); cout << livello: << pm->level << endl; break; 34

Funzioni Virtuali Il problema principale è che potrebbero esistere molti funzioni e/o metodi sparsi tra vari file sorgenti che effettuano test sul campo tipo La loro modifica potrebbe risultare difficoltosa, soprattutto quando si aggiungono nuovi tipi da gestire in seguito ad un estensione della gerarchia di classe 35

Funzioni Virtuali Le funzioni virtuali superano elegantemente il problema, consentendo al programmatore di dichiarare nella classe base dei metodi che saranno ridefiniti nelle classi derivate La corretta associazione tra il metodo chiamato tramite un puntatore ad una classe base e il metodo effettivo dell istanza appartenente alla gerarchia è effettuata in fase di esecuzione (meccanismo noto come dynamic binding) 36

Funzioni Virtuali Esempio: class Dipendente { char *nome, *cognome; public: virtual void stampa() const; /* */ }; La keyword virtual indica che stampa() è una sorta di interfaccia tra la funzione stampa() definita in questa classe e le funzioni stampa() definite nelle classi da questa derivate 37

Funzioni Virtuali Gli argomenti della funzione definita nella classe derivata non possono differire dagli argomenti della corrispondente funzione (dichiarata virtual) nella classe base Una funzione virtuale deve essere definita nella classe in cui essa è dichiarata per la prima volta (unica eccezione: caso in cui essa è dichiarata virtuale pura) Si può usare una funzione virtuale anche se non vi sono classi derivate da essa e non è necessario che le derivate ne forniscano versioni personalizzate 38

Funzioni Virtuali Grazie al meccanismo funzioni virtuali, posso richiamarle con un puntatore generico: Manager m1; Dipendente *pdip = &m1; pdip->stampa(); // viene richiamata la stampa() di Manager Nota: se stampa() non fosse stata dichiarata virtual, l istruzione precedente avrebbe richiamato stampa() di Dipendente! (meccanismo di static binding) 39

Funzioni Virtuali pure Alcune classi rappresentano dei concetti astratti, a cui non possono essere associate istanze di oggetti reali In altre parole, hanno senso solo come base per derivare altre classi, fornendo una sorta di interfaccia Generalmente, le classi astratte sono caratterizzate da funzioni virtuali pure: class Shape { public: virtual void rotate(float) = 0; virtual void draw() = 0; }; 40

Funzioni Virtuali pure Non è possibile avere istanze di classi astratte: Shape s; // errore di compilazione! Le funzioni virtuali pure DEVONO essere implementate in una classe derivata, altrimenti si hanno ancora classi astratte (e come tali non istanziabili) Un importante uso delle classi astratte è quello di fornire un interfaccia senza esporre dettagli di implementazione Le classi da essa derivate implementeranno le funzionalità specifiche 41

Funzioni Virtuali pure Esempio: Class Rectangle: public Shape { public: void rotate (float); void draw(); }; void Rectangle::draw() { // implementazione specifica per il rettangolo } void Rectangle::rotate(float degree) { // implementazione specifica per il rettangolo } 42

Eredità pubblica, privata e protetta La forma generale per derivare la classe D dalla classe B è la seguente: class D : specificatore B { // }; dove specificatore (opzionale) può essere private (per default), protected o public e indica il tipo di accesso che la classe derivata avrà nei confronti della classe base 43

Eredità pubblica, privata e protetta Se D deriva da B secondo lo specificatore public (è il più comune): le sezioni public e protected di B lo saranno anche in D protected: le sezioni public e protected di B diventeranno sezioni protected in D (chiusura verso l esterno ma non verso la gerarchia di eredità) private (default): le sezioni public e protected di B diventeranno sezioni private in D (chiusura totale) Nota: il private di B NON è mai accessibile dalla classe derivata D! 44

Conversioni personalizzate Il C++ consiste di ridefinire il comportamento degli operatori del linguaggio quando lavorano su tipi definiti dall utente Anche il cast (esplicito o meno) è un operatore, e come tale è ammessa la sua ridefinizione E dunque possibile definire come avviene la conversione tra un tipo ed un altro La sintassi generale è: operator tipo(); 45

Conversioni personalizzate Esempio: class Real { protected: float value; public: Real() : value(0) { } /* */ operator float() { return value; } }; Con la definizione precedente è possibile: Real a; float b = (float)a; // conversione esplicita float b = a + 3.5f; // conv. impl. di a in float 46