• Non ci sono risultati.

Relazioni tra classi ed ereditarietà

N/A
N/A
Protected

Academic year: 2021

Condividi "Relazioni tra classi ed ereditarietà"

Copied!
69
0
0

Testo completo

(1)

Relazioni tra classi ed ereditarietà

Michelangelo Diligenti Ingegneria Informatica e

dell'Informazione

diligmic@dii.unisi.it

(2)

Argomenti

Relazioni tra classi

Unified Modeling Language (UML)

Diversi tipi di relazione

Ereditarietà e funzioni virtuali

RTTI

(3)

Unified Modeling Language (UML)

Modello grafico per la modellazione delle classi e le loro relazioni

La modellazione di una classe

Dati e metodi listati uno per riga. La riga parte con

+ per membri e metodi pubblici

- per membri e metodi privati

# per membri e metodi

protected (vedremo cosa sono)

Nome classe Dati

Metodi

(4)

UML esempio singola classe

Persona -nome: string -altezza: int -peso: int

+posizione: float

<<constructor>>

+Person( )

+Person(name:string)

<<process>>

+Apprende( ):void

+PrendeAutobus(numero:int):void Suddivisione metodi per

Tipo di funzione svolta

Metodo specifica visibilità,

(5)

Relazioni tra classi

Supponiamo di dover modellare una macchina

Classe Car

Dobbiamo modellare le sue sottoparti

Classe Engine, Wheel, ecc.

Poi vi sono classi separate ma correlate

Ma anche Pedestrian, Strada, ecc.

E data una macchina ci sono tanti tipi

SUV, utilitaria, ecc.

O tante marche

(6)

Relazioni tra classi

Modellare dele sue sottoparti

Composizione

Poi vi sono classi separate ma correlate

Namespaces

Sottotipi (is-a relation)

Ereditarietà, la più importante!

(7)

Composizione

Supponiamo di dover modellare un aeroplano

Un aeroplano è formato da sottoparti: ali, coda, fusoliera, cabina, ecc.

Ogni parte può essere complessa ed essere formata da altre sottoparti

Quando si compone un oggetto mettendo insieme le sue sottoparti si parla di composizione

(8)

Composizione: C++ ed UML

class Aeroplano { private:

Fusoliera fusoliera;

Coda coda;

Ali ali;

}; Fusoliera

Aeroplano

Coda

(9)

Composizione in C++

Costruttori e inizializzazione delle sottoparti

Se l'utente non specifica nulla, il default costructor viene chiamato automaticamente

class Aeroplano { private:

Fusoliera fusoliera;

Coda coda;

Ali ali;

public:

Aeroplano() { }

Nessuna specifica, fusoliera e coda sono inizializzati con il proprio

costruttore di default

(10)

Composizione in C++: costruttori

Se si desidera un costruttore diverso, usare costruttori pre-run

Sottoparti inizializzate prima esecuzione del costruttore dell'oggetto composto

La sintassi del costruttore è la seguente NomeClasse([params]) :

subpart1(params), subpart2(params), ... { codice costruttore

}

(11)

Composizione in C++: costruttori

Esempio

class Aeroplano { private:

Fusoliera fusoliera;

Coda coda;

Ali ali;

public:

Aeroplano() : fusoliera(3), coda(2) { }

Aeroplano(int flen, int clen, int alen) : fusoliera(flen), coda(clen), ali(2, alen) { }

Aeroplano(int len) : ali(2, len), coda(2) { }

Chiama costruttore di default per ali, fusoliera e coda inizializzate con un costruttore che prende int in input

fusoliera e coda inizializzate con un costruttore che prende int in input, ali con costruttore che prende due int

NO! Errore in compilazione

(12)

Composizione in C++: costruttori

Esempio

class Aeroplano { private:

Fusoliera fusoliera;

Coda coda;

float carburante;

int num_motori;

public:

Aeroplano() : fusoliera(3), coda(2), carburante(100.0), num_motori(2) { } Aeroplano(const int fusoliera_lunghezza, const int num_motori_) :

fusoliera(fusoliera_lunghezza), coda(2),

carburante(100.0), num_motori(num_motori_) { }

Anche tipi base possono essere inizializzati in questo modo: chiamando i loro costruttori

(13)

Composizione in C++: costruttori

Possibile anche usare copy constructors in inizializzazione

Esempio

class Aeroplano { private:

Fusoliera fusoliera;

Coda coda;

Ali ali;

public:

...

Aeroplano(const Fusoliera& fus, const Coda& cod) : fusoliera(fus), coda(cod) { } };

Fusoliera e coda inizializzate con copy costrustor.

(14)

Composizione in C++: costruttori

Con allocazione dinamica delle sottoparti

Usare se alcune parti possono rimane indefinite

class Aeroplano { private:

Fusoliera* fusoliera;

Coda* coda;

Ali ali;

public:

Aeroplano() : fusoliera(NULL), coda(NULL) { }

Aeroplano(int flen, int clen, int alen) : coda(NULL), ali(2, alen) { fusoliera = new Fusoliera(flen);

if (clen > 0)

Alcuni oggetti sono puntatori, non nessariamente tutti inizializzati dai costruttori.

Esempio qui li lasciamo NULL, utile quando si vuole lasciare alcune parti non definite (lazy initialization)

(15)

Composizione in C++: distruttori

Quando un oggetto viene distrutto

Si esegue automaticamente il distruttore delle classi usate in composizione

Esempio: se una classe aeroplano contiene un istanza di classe Coda ed Ali

Viene invocato il distruttore per i dati membri:

~Coda() per la coda e ~Ali() per le ali

Questo avviene automaticamente se Coda ed Ali non sono stati allocati dinamicamente

cioe' come sulle slide viste in precedenza

se allocati dinamicamente sta a voi gestire la distruzione

(16)

Composizione in C++: distruttori

Con allocazione dinamica

class Aeroplano { private:

Fusoliera* fusoliera;

Coda* coda;

Ali ali;

public:

...

~Aeroplano() {

if (coda != NULL) delete coda;

if (fusoliera != NULL) delete fusoliera;

}

Distruzione distrugge i puntatori e non i valori puntati.

Deallocazione adesso non avviene automaticamente tramite le chiamate automatiche dei distruttori (per gli oggetti allocati dinamicamente)

La deallocazione la dovete fare voi

(17)

Ereditarietà

Supponiamo di aver modellato il concetto di Aeroplano

Adesso ci viene chiesto di modellare un aliante, un jet, od un aeroplano ad elica

Non ha senso ripartire da zero. L'aeroplano già modella dati comuni

Lunghezza fusoliera

Lunghezza ali

Numero passeggeri

Peso a pieno carico

ecc.

(18)

Ereditarietà

OO risolve questo problema definendo

Classe base (o classe padre o superclass): un aeroplano con dati e metodi comuni

Classe Aliante che eredita tutte le caratteristiche (dati e metodi) di Aeroplano ed eventualmente ne aggiunge altri (come vento_minimo)

Classe Jet che aggiunge un attributo di tipo MotoreJet

Classe AeroplanoElica che aggiunge attributo di tipo MotoreElica

(19)

Ereditarietà

Si estende una classe in un'altra più definita

Si parla di Ereditarietà

Permette di condividere similarità

Si riusa il codice!

Si modellano solo le differenze

La classe base viene detta superclasse o classe padre o classe base (base class, parent class, superclass)

Classe derivata viene detta sottoclasse o classe derivata o classe figlia (subclass, child class,

derived class)

(20)

Ereditarietà

Dati e metodi della classe base sono ereditati dalla figlia

Istanza della sottoclasse è istanza anche della classe padre

La sottoclasse però aggiunge dati e/o metodi nuovi

ATTENZIONE:

Costruttori e distruttori non sono ereditati, tranne quelli di default

Vanno ridefiniti se necessario

(21)

Ereditarietà e UML

UML rappresenta l'ereditarieta come una freccia dalla classe derivata e quella padre

Esempio, modellazione di forme geometriche Forma

Poligono Ellisse

Cerchio Quadrato Rombo Esagono

...

Generalizzazione Specializzazione

(22)

Ereditarietà in C++

In C++ si segnala che una classe eredita da un'altra usando la sintassi:

class ClasseFiglia : public ClassePadre { … };

Esempio

class Poligono { … };

class Rettangolo : public Poligono { … };

(23)

Ereditarietà: aggiunta dati e metodi

Classe figlia spesso aggiunge dati membri o metodi

class Forma {… class Cerchio : public Forma { private: private:

float area; float raggio;

public: public:

float Area() const; float Raggio() const;

}; };

Cerchio +raggio: float +Raggio(): float

Forma +area: float +Area(): float

(24)

Ereditarietà e C++

class Forma { ...

private:

int area;

int perimetro;

public:

Forma() { cout << sizeof(*this); } };

class Cerchio : public Forma { private:

float raggio;

public:

area

perimentro raggio

Forma

Cerchio

Nuovi dati sono aggiunti in coda sizeof(Cerchio) > sizeof(Forma)

(25)

Ereditarietà e costruttori in C++

Costruttore della classe padre è invocato nel costruttore con sintassi simile a composizione

NomeClasse([params]) : NomePadre([params]), subpart1(params), … { }

Composizione ed ereditarietà convivono

Esempio

class Poligono { class Rettangolo : public Poligono {

private: public:

int n_lati; Rettangolo() : Poligono(4) {}

public: };

Poligono(int n) { n_lati = n; }

(26)

Ereditarietà e costruttori in C++

Se non specificato diversamente, costruttori di default sono invocati

Però devono essere definiti, se non lo sono il compilatore emette un errore

Ordine chiamate da classe più generale a più specifica

Esempio data la struttura di classi in figura, si chiama:

1) Forma() 2) Ellisse() Forma

Ellisse

(27)

Ereditarietà e costruttori in C++

Tuttavia possibile specificare esplicitamente i costruttori da chiamare del padre

Obbligatorio se quelli di default non sono definiti

Si riusa la stessa sintassi per l'inizializzazione di dati membri

Forma

Ellisse

(28)

Ereditarietà e costruttori in C++

Esempio 2:

Forma

Ellisse

class Forma { private:

float area;

public:

Forma() : area(0.0) { }

Forma(const float area_) : area(area_) { } };

class Ellisse : public Forma { float fuoco1; float fuoco2;

public:

Ellisse() : fuoco1(0.0), fuoco2(0.0) { }

Ellisse(const float fuoco1_, const float fuoco2_, const float area_) : Forma(area_), fuoco1(fuoco1_), fuoco2(fuoco2_) { }

};

class Cerchio : public Ellisse { public:

(29)

Ereditarietà in C++: distruttori

Quando il distruttore per una classe viene invocato

Distruttore stesso viene eseguito

Si esegue il distruttore della classe padre

Ricorsivamente fino esecuzione distruttore della radice

Distruttori invocati dallo specifico al

generale risalendo l'albero delle relazioni

Data la struttura di classi in figura, si chiama

1) ~Cerchio() Forma

Ellisse

(30)

Ereditarietà in C++: protected

Oltre public e private vi è un altro livello di visibilità in C++

per membri e metodi: protected

Indica membro o metodo visibile solo a sottoclassi

Per il resto del mondo è non visibile come un private

class Ellisse : public Forma { protected:

float fuoco1;

float fuoco2;

public:

...

};

Questi membri sono visibili in classi derivate ma non per il resto del mondo.

Quanto si usa ereditarietà si tende ad usare più protected che private

(31)

Ereditarietà ed overriding

Si parla di overriding di metodi quando

Classe figlia ridefinisce metodo della classe padre

Con stessi argomenti

Diverso da overloading visto in precedenza

Overloading è nella stessa classe

Metodi hanno diversi argomenti

Cerchio +raggio: float

+Disegna() +Move(int) +Move(int, int)

Forma

Disegna()

(32)

Overriding e metodi del padre

Se necessario aggiungere codice a metodo del padre

Non necessario ridefinirlo nel figlio

Possibile invocarlo con sintassi

NomePadre::MetodoDaChiamare([params])

class Persona { private:

string nome;

string luogonascita;

public:

class Studente : public Persona { private:

string facolta; string corso;

public:

string ToString() const {

return Persona::ToString() + “\t” +

(33)

Esercizio

Creare una gerarchia di classi per gestire le persone che lavorano o studiano all'università di Siena

Persone sono studenti o ricercatori o docenti o ammistratori

Tutti hanno nome, luogo di nascita, indirizzo di lavoro

Studenti hanno inoltre facoltà e corso che frequentano

Ricercatori l'argomenti di ricerca

Docenti i corsi che insegnano

Amministrativi l'ufficio di appartenenza

Definire la gerarchia di classi, i dati membri, i metodi

(34)

Ereditarietà e polimorfismo

Esempio di ereditarietà ed overriding

class Poligono {… class Rettangolo : public Poligono { void Disegna(); void Disegna();

}; };

PROBLEMA: se un'istanza della classe figlia è anche istanza della classe padre quale metodo chiamo nei seguenti casi?

Poligono p; p.Disegna();

Rettangolo p; p.Disegna();

Poligono* p = new Rettangolo(); p->Disegna();

Secondo voi questi 3 casi sono

sintatticamente corretti?

(35)

Ereditarietà e polimorfismo

Obiettivo è sempre chiamare il metodo più specifico

PROBLEMA: il compilatore non può spesso sapere qual'è la classe più specifica

Poligono* p = NULL;

if (string(argv[1]) == “rettangolo”) p = new Rettangolo();

else if (string(argv[1]) == “poligono”) p = new Poligono();

p->Disegna();

La classe di p è definita a run-time!

Si, il casting è corretto, un Rettangolo è anche un Poligono

In base alle conoscenze attuali, qui si chiamerebbe sempre Disegna() di

Poligono, Anche se fosse un Rettangolo

(36)

Ereditarietà e polimorfismo

Compilatore non conosce la classe, ma sarebbe utile lo facesse. Esempio:

Poligono* p = NULL;

if (string(argv[1]) == “rettangolo”) p = new Rettangolo();

else if (string(argv[1]) == “poligono”) p = new Poligono();

p->Disegna();

p->CalcolaArea();

p->CalcolaPerimetro();

Paradigma di programmazione comune

Inizializzazione determina Tipo necessario nel contesto specifico

Utilizzo indipendente dal Tipo specifico creato.

(37)

Ereditarietà e polimorfismo

OO impone una diversità fondamentale:

il compilatore non decide quale metodo chiamare nel momento della compilazione

Si decide il metodo a run-time, quando l'oggetto è disponibile

Chiamato run-time, late, or dynamic binding

Polimorfismo:

2 oggetti di tipo Poligono rispondono diversamente allo stesso input (uno è anche Rettangolo e l'altro no)

Vedremo come realizzarlo in C++, come scritto nella

(38)

Ereditarietà e C++: funzioni virtuali

Meccanismo polimorfico di late-binding non è default in C++

Si realizza solo per i metodi virtuali

Esempio precedente

class Poligono {… class Rettangolo : public Poligono { void Disegna(); void Disegna();

}; };

Poligono* p = new Rettangolo();

(39)

Ereditarietà e C++: funzioni virtuali

Definire un metodo virtuale

richiede che sia specificata la keyword

virtual virtual

Esempio precedente

class Poligono { class Rettangolo : public Poligono {

int n_lati; float dim_lato;

virtual void Disegna(); virtual void Disegna();

virtual ~Poligono() { } virtual ~Rettangolo() { } }; };

Poligono* p = new Rettangolo();

p->Disegna(); Qui si chiama

Se ci sono metodi virtuali è obbligatorio definire un distruttore virtuale

(40)

Ereditarietà e C++: funzioni virtuali

In C++ il polimorfismo, meccanismo alla base dell'OO è opzionale!

Sembra strano

Causa di molti bugs, facile scordarsi di ”virtual”

Perché lo standard del C++ ha deciso questo?

Per capirlo dobbiamo studiare i dettagli di come il late-binding viene realizzato in C++

(41)

Vtables

Poiché il binding avviene a run-time

Ogni oggetto ha tabella di puntatori ai suoi metodi

Detta vtable n_lati

...

Vtable

Poligono

n_lati dim_lato

Vtable

Rettangolo

(42)

Vtables

Le vtables costano

Memoria: devo memorizzare un puntatore per metodo puntato

Tempo: ogni volta che un metodo è invocato, devo 1) accedere alla vtable

2) dereferenziare il puntatore

3) chiamare la funzione come sempre

Questo costa molto di più che fare solo 3)

Implicazione del run-time binding: la funzioni virtuali

(43)

Vtables

C++ vuole mantenere l'efficienza potenziale del C

Se non si usano metodi virtuali, usare il C++ non comporta penalita'

Pertanto il meccanismo del late-binding è opzionale in C++

Linguaggi puri OO lo hanno come default

Scelta molto controversa

Causa di bugs

La perdita di velocità è spesso trascurabile

(44)

Metodi e classi astratte

Talvolta una classe padre non può definire un metodo perché non abbastanza definita

Tuttavia tutti gli oggetti che deriveranno dalla classe devono avere una certa funzionalità

Classe padre definisce l'interfaccia

Forza ogni classe derivata ad implementare la funzionalità

Un metodo virtuale definito ma non implementato viene detto astratto (abstract)

(45)

Metodi e classi astratte

Non è possibile generare un oggetto di classe astratta

Non sarebbe possibile gestirlo a run-time: i metodi astratti non sono definiti

In C++ è però possibile definire una classe figlia non astratta, e fare il casting al padre di classe astratto

Metodi da chiamare sono definiti dalla vtable dell'istanza

In C++ le classi astratte sono maneggiabili solo per puntatore

(46)

Metodi astratti in C++

Si realizzano con la sintassi

virtual tipo NomeMetodo([args]) = 0;

Esempio class Forma {...

virtual void Disegna() = 0;

};

class Rettangolo : public Forma { virtual void Disegna() {...}

};

class Cerchio : public Forma {

Forma non ha informazioni per disegnare l'istanza ma ogni oggetto che deriva da Forma deve poter essere disegnato

Ogni oggetto derivato

da Forma deve implementare

(47)

Metodi astratti in C++

Non si può costruire ed usare un oggetto istanza solo della classe con metodi astratti

Solo la classe figlia che li implementa può essere costruita

Forma f; // NO! Forma ha metodi astratti Rettangolo r; // OK, Disegna è implementato

Forma* f = new Rettangolo(); // OK, f è Rettangolo, Disegna() trovato via vtable

Ecco perché i metodi astratti devono essere virtuali, per poter chiamare i

(48)

Metodi astratti in C++: esempio

class Forma {

virtual float Area() = 0;

virtual void Disegna() = 0;

};

class Rettangolo : public Forma {

virtual float Area() { … /* implementazione */ } virtual void Disegna() { … /* implementazione */ } };

class Cerchio : public Forma {

virtual float Area() { … /* implementazione */ }

(49)

Metodi astratti in C++: esempio

// Costruzione specifica sceglie una sottoclasse Forma* f = NULL;

if (string(argv[1]) == “rettangolo”) f = new Rettangolo();

else if (string(argv[1]) == “cerchio”) f = new Cerchio();

// Implementazione comune a tutti!

// Non va ripetuta.

cout << “Area “ << f->Area();

f->Disegna();

(50)

Classi pure astratte (pure abstract)

Possibile definire classi con solo metodi astratti

Dette classi pure astratte

Utili per definire interfacce

Delegano totalmente l'implementazione alle classi figlie

Esempio (come classe Forma precedente)

class DataCompressor { public:

virtual void Compress(const string& in_data, string* out_data) = 0;

(51)

Ereditarietà e casting

Upcasting passa da figlio a padre

operazione sicura e sempre accettabile

istanza di classe figlia è sempre istanza del padre

class Parent { }

class Child : public Parent { } Child c;

Parent* p = &c; // upcast

(52)

Ereditarietà e casting

Upcasting operazione fondamentale in C++

Pattern di programmazione tipico è il seguente

Si crea un istanza di classe figlia chiamando un costruttore specifico

Upcasting su puntatore di classe padre

Uso dell'istanza tramite l'interfaccia pubblica virtuale

Max flessibilità, cambiare istanza non tocca il codice

Si deve solo chiamare un costruttore diverso

Tutto il resto del codice rimane inalterato

(53)

Ereditarietà e casting

Esempio del meccanismo

class Forma {

virtual void Print() = 0;

};

class Rettangolo : public Forma {

virtual void Print() { cout << ”Rettangolo\n”;}

};

class Cerchio : public Forma {

virtual void Print() { cout << ”Cerchio\n”;}

};

int main() {

Forma* f = new Rettangolo();

f->Print();

Usare un cerchio invece che rettangolo cambia una sola riga

Creo oggetto figlio e faccio upcasting

Uso interfaccia pubblica e virtuale

(54)

Ereditarietà e casting

Downcasting passa da padre a figlio

operazione pericolosa

padre ha più figli e si potrebbe scegliere il figlio sbagliato

class Parent { }

class Child1 : public Parent { } class Child2 : public Parent { } Parent* p = new Child1;

Child1* c1 = (Child1*)p; // OK, p era un Child1 // ATTENZIONE la seguente linea compila

(55)

Esercizio

Creare una gerarchia di classi per gestire le persone che lavorano o studiano all'università di Siena

Persone sono o studenti o ricercatori o docenti o ammistratori

Tutti hanno nome, luogo di nascita, indirizzo di lavoro

Studenti hanno inoltre una facoltà e corso di laurea che frequentano

Ricercatori hanno degli argomenti di ricerca

Docenti hanno dei corsi che insegnano

Amministrativi hanno un ufficio di appartenenza

Deve essere possibile scrivere o leggere ogni persona su/da una stringa che ne codifica le sue caratteristiche

Nel main, chiedere all'utente se vuole generare uno studente, un ricercatore un docente o un amministrativo da linea di comando,

(56)

C++ e casting

Casting comune sorgente di bugs

Tipicamene sono bug difficili da tracciare

C ha solo un tipo di cast

C++ permette di fare cast in molti modi

static_cast

reinterpret_cast

const_cast

dynamic_cast

Sintassi

(57)

C++: casting e bugs

class CDummy { float i,j;

public:

Cdummy(const float i_, const float j_) : i(i_), j(j_) { } };

class CAddition { int x,y;

public:

CAddition (int a, int b) { x=a; y=b; } int result() { return x+y;}

};

int main () {

CDummy d(1.1, 2.2);

CAddition* padd = (CAddition*)&d;

cout << padd->result() << endl;

Casting trasforma Cdummy in Caddition reinterpretando i bytes. Ma va bene?

Se sizeof(CAddition) > sizeof(CDummy) ci potrebbe essere un fault!

Altrimenti il comportamento è

(58)

C++: static_cast

static_cast equivalente al cast del C

Efficiente non effettua alcun controllo di correttezza

Converte tipi se il casting è noto, esempio:

int i = 3;

float f = (float)i;

float f1 = static_cast<float>(i);

Oppure per convertire tipi che sono compatibili,esempio:

class CBase {};

(59)

C++: static_cast

Vantaggi di static_cast contro casting del C

Cast esplicito e visibile

si evita di non notare il cast

Possibile cercare in editor dove si fa casting per scovare i bug (difficile con cast del C)

Basta cercare la stringa static_cast

Efficienza analoga al cast C

Nessuna penalita', il casting avviene sempre in compilazione

(60)

C++: static_cast

Casting del C e static_cast sono pericolosi con classi

Esempio un down_cast class CBase {};

class CDerived: public CBase {};

CBase* a = new CBase;

CDerived* b = static_cast<CDerived*>(a);

Codice compila

(61)

C++: reinterpret_cast

reinterpret_cast: cast di basso livello

Per cast non ben definiti, sono cast pericolosi

Il compilatore reinterpreta bit a bit i dati di un tipo in un altro

Nessuna garanzia di portabilità

Non dovrebbe quasi mai servire di usare cast di questo genere, ma se proprio vi servisse...

Esempio char c[10];

int* i = reinterpret_cast<int*>(c);

(62)

C++: const_cast

const_cast: permette di togliere constness

Esempio

void Print (char* str) { std::cout << str << std::endl; } int main () {

const char* c = "sample text";

Print(const_cast<char*>(c));

return 0;

}

In questo caso la cosa giusta sarebbe cambiare void print (char * str) → void print (const char * str)

Talvolta questo non si può sempre fare

Print potrebbe essere in libreria esterna (legacy

(63)

C++: dynamic_cast

dynamic_cast: cast sicuro anche con ereditarietà

Effettua controlli di compatibilità a run-time tra i tipi prima di effettuare la conversione

Costoso dal punto di vista delle performance rispetto a cast a compile-time (senza controlli)

Usare solo per cast insicuri in presenza di ereditarietà

Per questo il C++ tiene altri tipi di cast che hanno minori penalità in termini di efficienza

Di nuovo il C++ cerca un compromesso tra pulizia dell'implementazione OO ed efficienza

(64)

RTTI e cast dinamici

Run Time Type Identification (RTTI)

Permette di identificare un oggetto a run-time in modo sicuro

Permette di controllare il tipo di un oggetto facendo il down casting in modo sicuro

Se il down cast fallisce assegna un puntatore NULL

In tal caso il programmatore sa che il casting non è andato a buon fine

(65)

RTTI e dynamic_cast

Esempio

class Forma { };

class Rettangolo : public Forma { };

class Cerchio : public Forma { };

int main() {

Forma* f = new Cerchio;

if (dynamic_cast<Rettangolo*>(f)) std::cout << ”Rettangolo\n”;

else if (dynamic_cast<Cerchio*>(f)) std::cout << ”Cerchio\n”;

delete f;

return 0;

(66)

RTTI e problemi

Non abusare di RTTI, se avete molte classi si crea catena di if/else

Si deve modificare il codice per ogni classe aggiunta

Meglio usare l'interfaccia della superclasse

Lo vediamo di nuovo nella slide successiva

Spesso sintomo di un design errato

Interfaccia della classe padre non completa o ben definita

Abuso dell'ereditarietà dove essa non serviva

(67)

RTTI e problemi

Esempio equivalente al precedente ma elegante, pulito e facile da mantenere

class Forma {

virtual void Print() const = 0;

};class Rettangolo : public Forma {

virtual void Print() const { cout << ”Rettangolo\n”; } };class Cerchio : public Forma {

virtual void Print() const { cout << ”Cerchio\n”; } };int main() {

Forma* f = new Cerchio;

f->Print();

return 1;

Completato l'interfaccia con un metodo virtuale che adesso viene invocato invece che

usare RTTI

(68)

RTTI: typeinfo

Usando la classe typeid, è possibile accedere ad informazioni di tipo che il C++ ha per ogni variabile

#include <typeinfo>

class CBase { virtual void f(){} };

class CDerived : public CBase {};

int main () {

CBase* a = new CBase;

CBase* b = new CDerived;

std::cout << "a is: " << typeid(a).name() << '\n';

std::cout << "*a is: " << typeid(*a).name() << '\n';

if (typeid(*a) != typeid(*b))

std::cout << "a and b are of different types:\n";

(69)

Ereditarietà multipla

C++ supporta ereditarietà multipla

Una classe può ereditare da due padri

Esempio

class Parent1 { } class Parent2 { }

class Child : public Parent1, public Parent2 { }

Caratteristica pericolosa che crea confusione

Piu' classi padre possono avere metodi o dati con lo stesso nome! Quale chiamo? Polimorfismo non ben definito

Usare tale caratteristica solo in casi eccezionali

Un oggetto Child è sia istanza di Parent1 che Parent2

Riferimenti

Documenti correlati

Al tempo stesso, l’assenza di accordi tra gli Stati europei (o la stessa Ue) e i Paesi di origine dei migranti irregolari, e la sostanziale incapacità politica ed economica di

I numeri che appartengono all’insieme N sono quei numeri che vengono semplicemente definiti NATURALI.I numeri naturali sono quelli che usiamo per contare gli oggetti.. Il primo

Più nel dettaglio, il veicolo aziendale strumentale in uso al dipendente o al collaboratore non è imponibile, in quanto l’utilizzo è esclusivamente lavorativo.. Il veicolo aziendale

E’ sempre possibile rendere positivo il coefficiente quadratico.. Infatti, nel caso in cui a &lt; 0 , basterà moltiplicare per -1

A tal proposito, si rammenta che l’Autorità, nelle «N uove linee guida per l’attuazione in generale, della normativa in materia di prevenzione della corruzione

nell'edificio dopo la costruzione di quest'ultimo per iniziativa di parte dei condomini, non rientra nella proprietà comune di tutti i condomini, ma appartiene in proprietà a quelli

Era infatti considerato un temibile oppositore, simbolo della lotta di liberazione dei neri.. 46 Quale di questi trittici di personaggi visse durante il «secolo

Il secondo momento della lotta cattolica arrivò all’inizio degli anni Novanta, quando alcuni pensatori cattolici, Michael Novak e padre Richard John Neuhaus, proposero il