• Non ci sono risultati.

Il meccanismo dell’ereditarietà

N/A
N/A
Protected

Academic year: 2021

Condividi "Il meccanismo dell’ereditarietà"

Copied!
26
0
0

Testo completo

(1)

Corso di Programmazione ad Oggetti

Il meccanismo dell’ereditarietà

a.a. 2008/2009

Claudio De Stefano

(2)

Ereditarietà

■ L’ereditarietà consente di definire nuove classi per specializzazione o estensione di classi preesistenti, in modo incrementale

■ Il meccanismo dell'ereditarietà è di fondamentale importanza nella programmazione ad oggetti, in quanto induce una strutturazione gerarchica nel sistema software da costruire

■ L’ereditarietà consente di realizzare relazioni tra classi di tipo generalizzazione- specializzazione, in cui una classe, detta base, realizza un comportamento generale comune ad un insieme di entità, mentre le classi derivate (sottoclassi) realizzano comportamenti specializzati rispetto a quelli della classe base

■ Esempio:

– Tipo o classe base: Animale

– Tipi derivati (sottoclassi): Cane, Gatto, Cavallo, …

■ In una gerarchia gen-spec, le classi derivate sono specializzazioni (cioè casi particolari) della classe base

(3)

Ereditarietà e tassonomie

■ Generalizzazione: dal particolare al generale

■ Specializzazione o particolarizzazione: dal particolare al generale

Generalizzazione Specializzazione

Nel paradigma a oggetti, col meccanismo dell’ereditarietà ci si concentra

sulla creazione di tassonomie del sistema in esame

(4)

Ereditarietà: esempio

Per descrivere un sistema sono possibili tassonomie diverse, a seconda degli obiettivi

Automobile Automobile

Benzina Berlina

Diesel Station Wagon

Oggetto Oggetto

Veicolo Veicolo

Veicolo Senza Motore Veicolo Senza

Motore Veicolo

A Motore Veicolo A Motore

Motocicletta

Motocicletta AutomobileAutomobile AereoAereo

(5)

Ereditarietà e riuso

■ Esiste però anche un altro motivo, di ordine pratico, per cui conviene usare l'ereditarietà, oltre quello di descrivere un sistema secondo un modello gerarchico; questo secondo motivo è legato esclusivamente al concetto di riuso del software

■ In alcuni casi si ha a disposizione una classe che non corrisponde esattamente alle proprie esigenze. Anziché scartare del tutto il codice esistente e riscriverlo, si può seguire con l'ereditarietà un approccio diverso, costruendo una nuova classe che eredita il comportamento di quella esistente, salvo che per i cambiamenti che si ritiene necessario apportare

■ Tali cambiamenti possono riguardare sia l'aggiunta di nuove

funzionalità che la modifica di quelle esistenti

(6)

Ereditarietà: vantaggi

■ In definitiva, l’ereditarietà offre il vantaggio di ridurre i tempi di sviluppo, in quanto minimizza la quantità di codice da scrivere quando occorre:

– definire un nuovo tipo che è un sottotipo di un tipo già disponibile, oppure – adattare una classe esistente alle proprie esigenze

■ Non è necessario conoscere in dettaglio il funzionamento del codice da riutilizzare, ma è sufficiente modificare (mediante aggiunta o specializzazione) la parte di interesse

(7)

■ L’esempio mostra come sia possibile derivare dalla classe Shape la classe

Circle:

L'ereditarietà in C++

class Shape { public:

Shape(Point& location, Color& color);

~Shape();

private:

Point location;

Color color;

};

class Circle : public Shape { public:

Circle(Point& location, Color& color, double radius);

~Circle();

private:

double radius;

};

I membri pubblici della classe base sono

membri pubblici della

classe derivata

(8)

L’accessibilità ai membri della classe base

Classe derivata Private

Public

non accessibili

Classe base

La classe derivata ha

accesso ai soli membri

public della classe base

public

(9)

Ereditarietà: I membri protected

accessibili alla sola classe derivata

I membri protected sono membri privati che risultano però accessibili alla classe derivata

class T { public:

protected:

private:

};

non accessibili

Private Public Protected

Classe derivata

Classe base

public

(10)

Ereditarietà

■ L’ereditarietà si presenta in tre distinte forme:

■ Nell’ereditarietà pubblica, i membri ereditati hanno la stessa protezione che avevano nella classe base

– gli utenti della classe derivata possono usare i membri pubblici ereditati

■ Nell’ereditarietà privata, i membri ereditati divengono membri privati della classe ereditata

– gli utenti della classe derivata non possono usare i membri ereditati

■ Nell’ereditarietà protetta, i membri pubblici e protetti ereditati divengono membri protetti della classe derivata

class B : public A { … };

class B : private A { … };

class B : protected A { … };

(11)

Ereditarietà

Tipo di

ereditarietà Classe base Classe derivata

public

public

protected private

public

protected inaccessibile protected

public

protected private

protected protected inaccessibile private

public

protected private

private

private

inaccessibile

(12)

Ereditarietà

I membri pubblici e protetti della classe base diventano membri

pubblici e protetti, rispettivamente, della classe derivata

public

Function

Private Public Protected

Private Public Protected

Class

public

(13)

Ereditarietà

I membri pubblici e protetti della classe base diventano membri privati della classe derivata

private

Function

Private Public Protected

Private Public Protected

Class

public

(14)

Ereditarietà

I membri pubblici e protetti della classe base diventano membri protetti della classe derivata

protected

Function

Private Public Protected

Private Public Protected

Class

public

(15)

Ereditarietà

I costruttori delle classi derivate

■ I costruttori non si ereditano, ma si ridefiniscono nelle classi derivate

■ Poiché l’oggetto della classe base deve esistere prima che possa essere trasformato in un oggetto della classe derivata

– il costruttore della classe base deve essere chiamato per creare l’oggetto della classe base prima che il costruttore della classe derivata possa essere chiamato

– in mancanza di una esplicita chiamata, da effettuarsi nella lista d’inizializzazione, il compilatore inserisce la chiamata del costruttore di default della classe base

Circle::Circle(Point& loc, Color& color, double rad) : Shape(loc, color), radius(rad) { };

Il costruttore della classe base è invocato al primo posto nella lista

d'inizializzazione

(16)

Ereditarietà

La regola vale per tutti i costruttori...

class DerivedIntArray: public intArray { int zzz;

public:

DerivedIntArray const& operator=(DerivedIntArray const &a) { intArray::operator=(a);

zzz = a.zzz;

return *this;

} };

Il costruttore di assegnazione della classe base è chiamato prima

ancora di assegnare valore al nuovo dato membro zzz

(17)

Ereditarietà

I distruttori delle classi derivate

■ L’invocazione del distruttore di una classe derivata produce automaticamente la chiamata di tutti i distruttori delle sue superclassi

– i distruttori sono chiamati secondo l’ordine che si ottiene risalendo via via la gerarchia delle classi

■ Pertanto, il distruttore di una classe derivata non deve invocare esplicitamente il distruttore della classe base, ma deve solo preoccuparsi delle azioni di pulizia relative

– ai nuovi dati membro introdotti nella classe derivata e

– ai file aperti dalle nuove funzioni membro della classe derivata

(18)

Ereditarietà

L’overriding delle funzioni membro

■ Una classe derivata può ridefinire funzioni membro già disponibili a livello della classe base

■ In questo caso, se è utile, la funzione originale è utilizzabile nella ridefinizione, grazie all’impiego dell’operatore di risoluzione dello scope ::

class ComplexPolygon : public Shape {

};

bool ComplexPolygon::containsPoint(Point& pt) { if(!Shape::containsPoint(pt)) return false;

// Do the precise check to see if pt is within the polygon }

Chiama la funzione membro della classe base

Non è una chiamata ricorsiva!

(19)

Ereditarietà

■ Un oggetto di una classe derivata può essere implicitamente convertito in un oggetto della classe base

– questa operazione di chiama upcasting, perché ci si muove verso l’alto nella gerachia delle classi

■ L’upcasting produce però l’object slicing, con perdita dei dati membro definiti a livello della classe derivata

– nell’esempio, solo i campi location e color di circle sono assegnati ai corrispondenti campi di shape,

e non il campo radius

Circle circle(pt, "Red", 5);

Shape shape = circle;

Un upcasting: l’oggetto della classe derivata circle è assegnato

all’oggetto della classe base shape

Per evitare l’object slicing, l’upcasting va usato solo con puntatori e riferimenti

(20)

Ereditarietà

Il binding statico è la norma

■ Anche impiegando puntatori e riferimenti, il C++ utilizza il binding statico per le funzioni membro “normali”

– la funzione membro invocata è quella associata al tipo statico dell’oggetto:

class Shape { … void draw() const; … };

class Circle : public Shape { … void draw() const; … };

class Square : public Shape { … void draw() const; … };

class Rectangle : public Shape { … void draw() const; … };

Shape* sL[3];

sL[0] = new Circle(…);

sL[1] = new Square(…);

sL[2] = new Rectangle(…);

sL[0]->draw();

sL[1]->draw();

sL[2]->draw();

Il binding dinamico è ottenibile solo Upcast a Shape*

La funzione chiamata è

sempre Shape::draw()

(21)

Ereditarietà

Il binding dinamico e il polimorfismo

■ Il polimorfismo e il binding dinamico si ottengono solo:

– dichiarando virtual le funzioni membro – operando tramite puntatori e riferimenti

class Shape { … virtual void draw() const; … };

class Circle : public Shape { … virtual void draw() const; … };

class Square : public Shape { … virtual void draw() const; … };

class Rectanglele : public Shape { … virtual void draw() const; … };

Shape* sL[3];

sL[0] = new Circle(…);

sL[1] = new Square(…);

sL[2] = new Rectangle(…);

sL[0]->draw();

sL[1]->draw();

sL[2]->draw();

Il modificatore virtual è essenziale solo nella classe base

Le funzioni membro delle classi derivate sono automaticamente virtual

Upcast a Shape*

Le funzioni chiamate sono di volta in volta diverse:

Circle::draw() Square::draw()

(22)

Ereditarietà

Solo i puntatori e i riferimenti sono polimorfi

■ Anche usando le funzioni virtuali, il polimorfismo funziona solo se si opera tramite puntatori e riferimenti:

Circle c;

Shape& s1 = c;

Shape* s2 = &c;

Shape s3 = c;

s1.draw();

s2->draw();

s3.draw();

Chiamano Circle::draw() Chiama Shape::draw()

Per le classi che si fondano sulle funzioni virtuali può essere opportuno

disabilitare i costruttori di copia e di assegnazione (si opera solo attraverso

puntatori)

(23)

Ereditarietà

Polimorfismo e flessibilità

■ Il rinvio a tempo di esecuzione della decisione su quale funzione chiamare, rende possibile compilare una funzione che esegue invocazioni di funzioni virtuali anche se la classe derivata che dovrà fornire la funzione non è stata ancora implementata o ancore neanche definita

■ Questa capacità è importante per i produttori di software che progettano librerie il cui sorgente deve rimanere proprietario

– Un cliente della libreria può sviluppare classi derivate e ottenere che queste usino le funzioni della libreria senza avere alcun bisogno di accedere ai file d’implementazione

(24)

Ereditarietà

L'implementazione delle funzioni virtuali

■ L’uso delle funzioni virtuali produce un piccolo overhead:

– Per ogni classe polimorfa, esiste a run-time una tabella (v-table), contenente puntatori al codice delle funzioni virtuale

– Nelle varie classi derivate, ogni funzione (ad esempio draw)) occupa in tutte le v-table la stessa posizione (ad esempio 3)

– Ogni oggetto di una classe polimorfa ha un puntatore alla v-table

– Il compilatore traduce la chiamata di una funzione virtuale (ad esempio draw) nella forma “chiama la f. virtuale di offset 3

– A run-time, si segue il link e si determina il codice da chiamare

data vtable ptr

data

ptr to code ptr to code

...

object

01001 01011 11010 01001

01001 01011 11010 01001

(25)

Ereditarietà

Classi polimorfe, costruttori e distruttori

■ Un costruttore non può essere virtuale, in quanto ogni costruttore deve esattamente conoscere la struttura dell’oggetto da creare

■ Un distruttore può essere virtuale ed è opportuno che lo sia

class Shape { … virtual ~Shape(); };

Shape::~Shape() {cout << " there";}

Circle::~Circle() {cout << "hello";}

Shape *s = new Circle();

delete s;

hello there

Se i distruttori non sono virtuali si visualizza solo

there

Se i distruttori sono

virtuali si visualizza

(26)

Ereditarietà

Le funzioni virtuali pure

class Shape { public:

Shape(Location const &loc, Color const &color);

virtual ~Shape() { };

virtual Location getLocation() const;

virtual Color getColor() const;

virtual void draw() const = 0;

virtual bool intersect(Shape const *shape) const = 0;

};

class Circle: public Shape {

virtual void draw() const;

virtual bool intersect(Shape const *shape) const;

};

Un distruttore non può essere puro

Riferimenti

Documenti correlati

0789/2021, presentata da Catherine Duffy, cittadina irlandese, sulla qualità difettosa di alcuni blocchi da costruzione dovuta alla presenza di mica in Irlanda..

Tale strumento indiretto assumerebbe la forma di un prestito per la ricapitalizzazione bancaria, accanto ai tre strumenti esistenti per l'assistenza finanziaria nel quadro della

 Sono eletti in numero di due dagli studenti di ogni Istituto superiore di secondo grado della Provincia e, assieme ai rappresentanti degli altri Istituti, formano la

Sembrerebbe che l'introduzione di tale imposta possa aver contribuito all'aumento dei livelli di traffico attraverso Watergrasshill e che tali flussi nel suddetto paese siano con

Per assicurare che la normativa dell'UE sia applicata correttamente, la Commissione sta sottoponendo diversi Stati membri a controlli, nel contesto di un progetto

In linea di principio, pertanto, i residenti spagnoli che percepiscono pensioni dalla Spagna sono soggetti all'imposta sul reddito delle persone fisiche sul

La Commissione deciderà in merito alle future iniziative da intraprendere una volta conclusa l'analisi della situazione del lavoro a tempo determinato nel settore

Le autorità spagnole segnalano che la dichiarazione d'impatto ambientale per questo progetto è stata emanata con decisione del 30 giugno 2004 e che il demanio pubblico è