giorno 10 ereditarietà, funzioni virtuali e polimorfismo · ereditarietà, funzioni virtuali e...

39
Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo Questo capitolo si occupa di tre elementi fondamentali del linguaggio C++ in stretta relazione con la programmazione a oggetti: l’ereditarietà, le funzioni virtuali e il polimorfismo. L’ereditarietà è quella caratteristica che consente a una classe di ereditare le caratteristiche di un’altra classe. Utilizzando l’ereditarietà si può creare una classe generale che definisce i tratti in comune di un insieme di elementi correlati. Questa classe può quindi essere ereditata da altre classi più specializzate, ognuna delle quali aggiunge elementi specifici. Le funzioni virtuali si basano pro- prio sulla funzionalità dell’ereditarietà, per supportare il polimorfismo, ovvero la filosofia “un’interfaccia, più metodi” tipica della programmazione a oggetti. Argomenti del capitolo Elementi di base dell’ereditarietà Classi base e classi derivate Uso dell’accesso protetto Chiamata ai costruttori della classe base Creazione di una gerarchia di classi multilivello Puntatori della classe base che puntano a oggetti di classi derivate Creazione di funzioni virtuali Uso di funzioni virtuali pure e di classi astratte Il polimorfismo Gli elementi di base dell’ereditarietà Nel gergo del linguaggio C++, una classe ereditata è chiamata classe base. La classe che eredita le caratteristiche della classe base è chiamata classe derivata. Pertanto una classe derivata rappresenta una versione specializzata di una classe base. Una

Upload: truongnguyet

Post on 14-Feb-2019

230 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Giorno 10

Ereditarietà, funzioni virtualie polimorfismo

Questo capitolo si occupa di tre elementi fondamentali del linguaggio C++ in strettarelazione con la programmazione a oggetti: l’ereditarietà, le funzioni virtuali e ilpolimorfismo. L’ereditarietà è quella caratteristica che consente a una classe diereditare le caratteristiche di un’altra classe. Utilizzando l’ereditarietà si può creareuna classe generale che definisce i tratti in comune di un insieme di elementicorrelati. Questa classe può quindi essere ereditata da altre classi più specializzate,ognuna delle quali aggiunge elementi specifici. Le funzioni virtuali si basano pro-prio sulla funzionalità dell’ereditarietà, per supportare il polimorfismo, ovvero lafilosofia “un’interfaccia, più metodi” tipica della programmazione a oggetti.

Argomenti del capitolo• Elementi di base dell’ereditarietà

• Classi base e classi derivate

• Uso dell’accesso protetto

• Chiamata ai costruttori della classe base

• Creazione di una gerarchia di classi multilivello

• Puntatori della classe base che puntano a oggetti di classi derivate

• Creazione di funzioni virtuali

• Uso di funzioni virtuali pure e di classi astratte

• Il polimorfismo

Gli elementi di base dell’ereditarietàNel gergo del linguaggio C++, una classe ereditata è chiamata classe base. La classeche eredita le caratteristiche della classe base è chiamata classe derivata. Pertantouna classe derivata rappresenta una versione specializzata di una classe base. Una

Page 2: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

334 Giorno 10

classe derivata eredita tutti i membri definiti dalla classe base, ai quali aggiunge ipropri elementi specifici.Il linguaggio C++ implementa l’ereditarietà consentendo a una classe di incorporareun’altra classe nella propria dichiarazione. L’operazione viene eseguita specifican-do una classe base nel momento in cui si dichiara una classe derivata. Per iniziareecco un breve esempio che illustra vari concetti chiave dell’ereditarietà. Il seguenteprogramma crea una classe base chiamata TwoDShape che conserva la larghezza el’altezza di un oggetto bidimensionale e una classe derivata chiamata Triangle. Sifaccia particolare attenzione al modo in cui viene dichiarata Triangle.

// Una semplice gerarchia di classi.

#include <iostream>#include <<cstring>>using namespace std;

// Una classe per oggetti bidimensionali.class TwoDShape {public: double width; double height;

void showDim() { cout << “La larghezza e l’altezza sono “ width “ e “ height << “\n”; }};

// Triangle deriva da TwoDShape.class Triangle : public TwoDShape {public: char style[20];

double area() { return width * height / 2; }

void showStyle() { cout << “Il triangolo è “ << style << “\n”; }};

int main() { Triangle t1; Triangle t2;

t1.width = 4.0; t1.height = 4.0; strcpy(t1.style, “isoscele”);

t2.width = 8.0; t2.height = 12.0; strcpy(t2.style, “rettangolo”);

Triangle ereditaTwoDShape. Si notila sintassi.

Triangle può far riferimento aimembri di TwoDShape in quantofanno parte di Triangle.

Tutti i membri di Triangle sonodisponibili per gli oggetti Triangle,anche quelli ereditati da TwoShape.

Page 3: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 335

cout << “Informazioni per t1:\n”; t1.showStyle(); t1.showDim(); cout << “L’area è “ << t1.area() << “\n”;

cout << “\n”; cout << “Informazioni per t2:\n”; t2.showStyle(); t2.showDim(); cout << “L’area è “ << t2.area() << “\n”;

return 0;}

Ecco l’output del programma:

Informazioni per t1:Il triangolo è isosceleLa larghezza e l’altezza sono 4 e 4L’area è 8

Informazioni per t2:Il triangolo è rettangoloLa larghezza e l’altezza sono 8 e 12L’area è 48

Qui TwoDShape definisce gli attributi di una forma bidimensionale “generica”, chedunque può essere un quadrato, un rettangolo, un triangolo e così via. La classeTriangle crea un tipo specifico di TwoDShape, in questo caso un triangolo. La classeTriangle include tutti gli elementi di TwoDShape cui aggiunge il campo style, lafunzione area() e la funzione showStyle(). Il campo style, contiene una descri-zione del tipo del triangolo; la funzione area() calcola e restituisce l’area del trian-golo e la funzione showStyle() visualizza lo stile del triangolo.La riga seguente mostra come la classe Triangle eredita da TwoDShape:

class Triangle : public TwoDShape {

Qui TwoDShape è la classe base ereditata da Triangle che è la classe derivata. Comesi può vedere in questo esempio, la sintassi dell’ereditarietà è molto semplice efacile da usare.Poiché Triangle include tutti i membri della sua classe base, TwoDShape, può acce-dere a width e height all’interno di area(). Inoltre, in main(), gli oggetti t1 e t2possono fare riferimento direttamente a width e height in quanto fanno parte diTriangle. La Figura 10.1 rappresenta il modo in cui TwoDShape viene incorporata inTriangle.Un’ultima considerazione: anche se TwoDShape è la classe base di Triangle, è ancheuna classe completamente indipendente. Il fatto che sia una classe base per unaclasse derivata non significa che non possa essere utilizzata così com’è.Ecco la forma generale del meccanismo di ereditarietà:

class classe-derivata : accesso classe-base { // corpo della classe derivata}

Page 4: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

336 Giorno 10

Qui la parte accesso è opzionale ma, quando è presente, deve essere public, pri-vate o protected. Si parlerà meglio di queste opzioni più avanti in questo stessocapitolo. Per il momento si può dire che tutte le classi ereditate utilizzeranno laparola riservata public. Questo significa che tutti i membri pubblici della classebase saranno anche membri pubblici della classe derivata.Un grande vantaggio dell’ereditarietà è il fatto che una volta che si è creata unaclasse base che definisce gli attributi comuni a un insieme di oggetti, questa puòessere utilizzata per creare un numero qualsiasi di classi derivate specializzate.Ogni classe derivata può personalizzare a piacere le proprie caratteristiche. Peresempio, ecco un’altra classe derivata da TwoDShape che incapsula il concetto direttangolo:

// Una classe derivata da TwoDShape per i rettangoli.class Rectangle : public TwoDShape {public: bool isSquare() { if(width == height) return true; return false; }

double area() { return width * height; }};

La classe Rectangle include TwoDShape, cui aggiunge le funzioni isSquare(), chedetermina se il rettangolo è in realtà un quadrato, e area(), che calcola l’area delrettangolo.

Accesso ai membri ed ereditarietàCome si è detto nel Giorno 8, i membri di una classe vengono spesso dichiaraticome privati per evitare ogni utilizzo errato o non autorizzato. Il fatto di ereditareuna classe non rappresenta una violazione alla restrizione riguardante l’accessoprivato. Pertanto, anche se una classe derivata include tutti i membri della suaclasse base, non può accedere ai suoi membri privati. Per esempio, se width e

Figura 10.1Rappresentazione concettuale della classe Triangle.

Page 5: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 337

height in TwoDShape vengono resi privati come nel seguente esempio, Trianglenon sarà più in grado di accedervi.

// Le classi derivate non hanno accesso ai membri privati.

class TwoDShape { // ora sono private double width; double height;public: void showDim() { cout << “La larghezza e l’altezza sono “ << width << “ e “ << height << “\n”; }};

// Triangle deriva da TwoDShape.class Triangle : public TwoDShape {public: char style[20];

double area() { return width * height / 2; // Errore! Violazione d’accesso. }

void showStyle() { cout << “Il triangolo è “ << style << “\n”; }};

La classe Triangle non può essere compilata poiché i riferimenti a width e heightall’interno della funzione area() provocano una violazione d’accesso. Poiché orawidth e height sono private, sono accessibili solo dalle altre funzioni membro dellaclasse e dunque le classi derivate non vi avranno accesso.A prima vista può sembrare una grave restrizione il fatto che le classi derivate nonabbiano accesso ai membri privati della loro classe base, poiché ciò impedirebbel’uso dei membri privati in molte situazioni. Fortunatamente non è così, poiché illinguaggio C++ offre varie soluzioni. Una consiste nell’impiego di membri protetticome descritto nel prossimo paragrafo. Una seconda soluzione consiste nell’impie-go di funzioni pubbliche che forniscono l’accesso ai dati privati. Come si è visto neicapitoli precedenti, in genere i programmatori si preoccupano di garantisre l’acces-so ai membri privati di una classe tramite l’impiego di funzioni. Le funzioni chegarantiscono l’accesso ai dati privati sono chiamate funzioni d’accesso. Ecco unanuova versione della classe TwoDShape che aggiunge le funzioni d’accesso per imembri width e height:

// Accesso ai dati privati tramite funzioni d’accesso.

#include <iostream>#include <cstring>using namespace std;

// Una classe per oggetti bidimensionali.class TwoDShape {

Ora width e heightsono private.

Non è possibile accedereai membri privati di unaclasse base.

Page 6: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

338 Giorno 10

// queste sono private double width; double height;public:

void showDim() { cout << “La larghezza e l’altezza sono “ << width << “ e “ << height << “\n”; }

// funzioni d’accesso double getWidth() { return width; } double getHeight() { return height; } void setWidth(double w) { width = w; } void setHeight(double h) { height = h; }};

// Triangle deriva da TwoDShape.class Triangle : public TwoDShape {public: char style[20];

double area() { return getWidth() * getHeight() / 2; }

void showStyle() { cout << “Il triangolo è “ << style << “\n”; }};

int main() { Triangle t1; Triangle t2;

t1.setWidth(4.0); t1.setHeight(4.0); strcpy(t1.style, “isoscele”);

t2.setWidth(8.0); t2.setHeight(12.0); strcpy(t2.style, “rettangolo”);

cout << “Informazioni per t1:\n”; t1.showStyle(); t1.showDim(); cout << “L’area è “ << t1.area() << “\n”;

cout << “\n”; cout << “Informazioni per t2:\n”; t2.showStyle(); t2.showDim(); cout << “L’area è “ << t2.area() << “\n”;

return 0;}

Le funzioni d’accessoper width e height.

Usa le funzioni diaccesso per ottenere ilvalore della larghezza edell’altezza.

Page 7: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 339

Domande e risposte

D. Nelle discussioni riguardanti la programmazione Java ho sentito parlaredei termini superclasse e sottoclasse. Questi termini hanno un signifi-cato anche in C++?

R. Ciò che in Java viene chiamato superclasse, in C++ viene chiamato classe base.Ciò che in Java viene chiamato sottoclasse, in C++ si chiama classe derivata.Talvolta capita di sentire la stessa terminologia impiegata in entrambi i linguag-gi ma questo testo continuerà a utilizzare i termini standard per il linguaggioC++. A proposito, anche nel linguaggio C# vengono usati i termini classe basee classe derivata.

Verifica10.1 In quale modo una classe base viene ereditata da una classe derivata?10.2 Una classe derivata include i membri della sua classe base?10.3 Una classe derivata ha accesso ai membri privati della sua classe base?

Controllo degli accessi alla classe baseCome si è detto, quando una classe ne eredita un’altra, i membri della classe basedivengono i membri della classe derivata. Tuttavia l’accessibilità dei membri dellaclasse base all’interno della classe derivata è determinata dallo specificatore d’ac-cesso utilizzato nel momento in cui si è ereditata la classe base. Lo specificatored’accesso alla classe base può essere public, private o protected. Se lo specifica-tore d’accesso non viene indicato, allora se la classe derivata è una classe verràutilizzato lo specificatore private; se la classe derivata è una struttura, verrà utiliz-zato lo specificatore d’accesso public. Ecco quali sono le conseguenze dell’utilizzodegli specificatori d’accesso public o private (lo specificatore protected verràdescritto nel prossimo paragrafo).Quando una classe base viene ereditata come public, tutti i membri pubblici dellaclasse base divengono membri pubblici della classe derivata. Per il resto, gli ele-menti privati della classe base rimarranno privati di tale classe e dunque non saran-no accessibili da parte dei membri della classe derivata. Per esempio, nel seguenteprogramma, i membri pubblici di B divengono membri pubblici di D. Pertanto risul-teranno accessibili dalle altre parti del programma.

// L’ereditarietà pubblica.

#include <iostream>using namespace std;

class B { int i, j;public: void set(int a, int b) { i = a; j = b; }

Page 8: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

340 Giorno 10

void show() { cout << i << “ “ j << “\n”; }};

class D : public B { int k;public: D(int x) { k = x; } void showk() { cout << k << “\n”; }

// i = 10; // Errore! i è privata di B ➥ e non è possibile accedervi.

};

int main(){ D ob(3);

ob.set(1, 2); // accesso al membro della classe base ob.show(); // accesso al membro della classe base

ob.showk(); // usa il membro della classe base

return 0;}

Poiché set() e show() sono pubbliche di B, possono essere richiamate su un ogget-to di tipo D dall’interno di main(). Poiché i e j sono specificate come private,rimarranno privati di B. Questo è il motivo per cui la riga:

// i = 10; // Errore! i è privata di B e non è possibile accedervi.

è stata trasformata in un commento: perché D non può accedere a un membroprivato di B.L’opposto dell’ereditarietà pubblica è l’ereditarietà privata. Quando la classe baseviene ereditata con private, allora tutti i membri pubblici della classe base diven-gono membri privati della classe derivata. Per esempio, il programma rappresenta-to di seguito non può essere compilato poiché sia set() che show() sono membriprivati di D e non possono essere richiamati da main().

// Uso dell’ereditarietà privata. Questo programma non verrà compilato.

#include <iostream>using namespace std;

class B { int i, j;public: void set(int a, int b) { i = a; j = b; } void show() { cout << i << “ “ << j << “\n”; }};

// Gli elementi pubblici di B diventano privati in D.class D : private B { int k;

Qui B vieneereditata comepubblica.

Impossibile accedere ai poiché è privata di B.

Ora eredita B inmodo privato.

Page 9: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 341

public: D(int x) { k = x; } void showk() { cout << k << “\n”; }};

int main(){ D ob(3);

ob.set(1, 2); // Errore, impossibile accedere a set() ob.show(); // Errore, impossibile accedere a show()

return 0;}

Per ricapitolare: quando una classe base viene ereditata in modo privato, i membripubblici della classe base divengono membri privati della classe derivata. Questosignifica che rimangono accessibili da parte dei membri della classe derivata manon dalle altre parti del programma.

Uso di membri protettiCome si sa, un membro privato di una classe base non è accessibile da una classederivata. Si potrebbe pensare che per fare in modo che una classe derivata abbiaaccesso ad alcuni membri della sua classe base, sia necessario rendere pubblici talimembri. Naturalmente il fatto di rendere pubblico un elemento lo rende disponibi-le anche al codice esterno e non sempre questa è una situazione accettabile. Fortu-natamente non è così, poiché il linguaggio C++ consente di creare dei membriprotetti. Un membro protetto è pubblico nella gerarchia di classi ma privato al-l’esterno di questa gerarchia.Per creare un membro protetto si utilizza il modificatore d’accesso protected. Quandoun membro di una classe viene dichiarato come protected, tale membro sarà priva-to a tutti gli effetti, tranne quando viene ereditato. Dunque il membro protetto dellaclasse base risulterà accessibile dalla classe derivata. Pertanto utilizzando la parolariservata protected è possibile creare membri di classi che sono privati della classema che possono comunque essere ereditati e utilizzati da una classe derivata. Lospecificatore protected può essere utilizzato anche nelle strutture.Si consideri il seguente programma:

// Uso dei membri protected.

#include <iostream>using namespace std;

class B {protected: int i, j; // private di B, ma accessibili da Dpublic: void set(int a, int b) { i = a; j = b; } void show() { cout << i << “ “ << j << “\n”; }};

Ora set() e show()risultano inaccessibilida D.

Qui i e j sono protette.

Page 10: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

342 Giorno 10

class D : public B { int k;public: // D può accedere a i e j di B void setk() { k = i*j; }

void showk() { cout << k << “\n”; }};

int main(){ D ob;

ob.set(2, 3); // OK, set() è pubblica in B ob.show(); // OK, show() è pubblica di B

ob.setk(); ob.showk();

return 0;}

Qui, poiché B viene ereditata da D in modo pubblico e poiché i e j sono dichiaratecome protette, la funzione setk() di D potrà accedervi. Se i e j fossero statedichiarate come private da B, allora D non avrebbe potuto accedervi e il programmanon potrebbe essere compilato.

Domande e risposte

D. Si può ricapitolare il discorso dei membri pubblici, protetti e privati?

R. Quando un membro di una classe viene dichiarato come public, risulta acces-sibile da ogni altra parte del programma. Quando è dichiarato come private,risulta accessibile solo dai membri della sua stessa classe. Neppure le classiderivate avranno accesso ai membri privati della loro classe base. Se invece unmembro viene dichiarato come protected, può essere utilizzato solo dai mem-bri della sua classe e delle sue classi derivate. Pertanto la parola riservata pro-tected consente di ereditare un membro che tuttavia rimarrà privato della ge-rarchia di classi.Quando una classe base viene ereditata utilizzando public, i suoi membri pub-blici divengono membri pubblici della classe derivata e i suoi membri protettidivengono membri protetti della sua classe derivata. Quando una classe baseviene ereditata utilizzando protected, i suoi membri pubblici e protetti diven-gono membri protetti della classe derivata. Quando una classe base viene ere-ditata utilizzando la parola riservata private, i suoi membri pubblici e protettidivengono membri privati della classe derivata. In ogni caso, i membri privatidi una classe base rimangono privati della classe base.

Quando una classe base viene ereditata in modo pubblico, i membri protetti dellaclasse base divengono membri protetti della classe derivata. Quando una classebase viene ereditata come privata, i membri protetti della classe base divengonomembri privati della classe derivata.

D può accedere a i e jpoiché ora sonoprotette, non private.

Page 11: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 343

Lo specificatore d’accesso protected può essere specificato ovunque nella dichia-razione di una classe, anche se in genere si trova dopo che sono stati dichiarati imembri standard privati e prima dei membri pubblici. Pertanto ecco la forma piùcomune della dichiarazione di una classe:

class nome-classe{ // membri privati per defaultprotected: // membri protettipublic: // membri pubblici};

Naturalmente la categoria protected è opzionale.Oltre a specificare lo stato di protezione per i membri di una classe, la parolariservata protected può anche fungere da specificatore d’accesso quando si ereditauna classe base. Quando una classe base viene ereditata con protected, tutti imembri pubblici e protetti della classe base divengono membri protetti della classederivata. Nell’esempio precedente, se T ereditasse B nel seguente modo:

class D : protected B {

allora tutti i membri di B diverrebbero membri protetti di D.

Verifica10.4 Quando una classe base viene ereditata con private, i membri pubblici della

classe base divengono membri privati della classe derivata. Vero o falso?

10.5 Può un membro privato di una classe base essere reso pubblico tramite l’ere-ditarietà?

10.6 Quale specificatore d’accesso si deve utilizzare per fare in modo che un mem-bro sia accessibile all’interno della gerarchia ma privato all’esterno?

Costruttori ed ereditarietàIn una gerarchia è possibile che le classi base e le classi derivate abbiano ciascunaun proprio costruttore. Questo solleva un problema importante: quale costruttore èresponsabile della creazione di un oggetto della classe derivata? Quello della classebase, quello della classe derivata o entrambi? La risposta è questa: il costruttoredella classe base costruisce la porzione della classe base dell’oggetto e il costruttoreper la classe derivata costruisce la parte aggiunta dalla classe derivata. Tutto ciò hasenso poiché la classe base non conosce né ha accesso agli elementi della classederivata. Pertanto la costruzione degli elementi deve rimanere distinta. Gli esempiprecedenti si basavano sui costruttori standard creati automaticamente dal linguag-gio C++ e dunque questo non rappresentava un problema. Ma in pratica, la mag-gior parte delle classi utilizzerà dei costruttori. In questo paragrafo si vedrà comegestire questa situazione.

Page 12: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

344 Giorno 10

Quando il costruttore è definito solo dalla classe derivata, l’operazione è semplice:basta costruire l’oggetto della classe derivata. La parte relativa alla classe base verràcostruita automaticamente utilizzando il costruttore standard. Per esempio, eccouna versione rielaborata di Triangle che definisce un costruttore. Inoltre rendestyle privata, in quanto ora viene impostata dal costruttore.

// Aggiunge un costruttore a Triangle.

#include <iostream>#include <cstring>using namespace std;

// Una classe per oggetti bidimensionali.class TwoDShape { // queste sono private double width; double height;public: void showDim() { cout << “La larghezza e l’altezza sono “ << width << “ e “ << height << “\n”; }

// funzioni d’accesso double getWidth() { return width; } double getHeight() { return height; } void setWidth(double w) { width = w; } void setHeight(double h) { height = h; }};

// Triangle deriva da TwoDShape.class Triangle : public TwoDShape { char style[20]; // ora è privatopublic:

// Costruttore di Triangle. Triangle(char *str, double w, double h) { // Inizializza la porzione per la classe base. setWidth(w); setHeight(h);

// Inizializza la porzione per la classe derivata. strcpy(style, str); }

double area() { return getWidth() * getHeight() / 2; }

void showStyle() { cout << “Il triangolo è “ << style << “\n”; }};

int main() { Triangle t1(“isoscele”, 4.0, 4.0); Triangle t2(“rettangolo”, 8.0, 12.0);

Inizializza la parte TwoDShapedi Triangle.

Inizializza style che èspecifica di Triangle.

Page 13: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 345

cout << “Informazioni per t1:\n”; t1.showStyle(); t1.showDim(); cout << “L’area è “ << t1.area() << “\n”;

cout << “\n”; cout << “Informazioni per t2:\n”; t2.showStyle(); t2.showDim(); cout << “L’area è “ << t2.area() << “\n”;

return 0;}

Qui il costruttore di Triangle inizializza i membri di TwoDShape che eredita insiemeal suo campo style.Quando i costruttori sono definiti sia dalla classe base che dalla classe derivata,l’operazione è un po’ più complessa, poiché devono essere eseguiti entrambi icostruttori.

Chiamata dei costruttori della classe baseQuando una classe base ha un costruttore, la classe derivata deve richiamarlo espli-citamente per inizializzare la porzione dell’oggetto relativa alla classe base. Unaclasse derivata può richiamare un costruttore definito dalla sua classe base utiliz-zando una versione espansa della dichiarazione del costruttore della classe deriva-ta. Ecco la forma generale di questa dichiarazione espansa:

costruttore-derivato(elenco-argomenti) : costr-base(elenco-argomenti);{ corpo del costruttore derivato}

Qui costr-base è il nome della classe base ereditata dalla classe derivata. Si noti chei due elementi sono separati dal segno di “:” che separa la dichiarazione del costrut-tore della classe derivata dal costruttore della classe base. Se una classe eredita dapiù di una classe base, allora i costruttori della classe base sono separati l’unodall’altro da una virgola.Il seguente programma mostra come passare argomenti a una classe base o a uncostruttore della classe base. Il programma definisce un costruttore per TwoDShapeche inizializza le proprietà width e height.

// Aggiunge un costruttore a TwoDShape.

#include <iostream>#include <cstring>using namespace std;

// Una classe per oggetti bidimensionali.class TwoDShape { // queste sono private double width; double height;

Page 14: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

346 Giorno 10

public:

// Costruttore di TwoDShape. TwoDShape(double w, double h) { width = w; height = h; }

void showDim() { cout << “La larghezza e l’altezza sono “ << width << “ e “ << height << “\n”; }

// funzioni d’accesso double getWidth() { return width; } double getHeight() { return height; } void setWidth(double w) { width = w; } void setHeight(double h) { height = h; }};

// Triangle deriva da TwoDShape.class Triangle : public TwoDShape { char style[20]; // ora è privatopublic:

// Costruttore di Triangle. Triangle(char *str, double w, double h) : TwoDShape(w, h) { strcpy(style, str); }

double area() { return getWidth() * getHeight() / 2; }

void showStyle() { cout << “Il triangolo è “ << style << “\n”; }};

int main() { Triangle t1(“isoscele”, 4.0, 4.0); Triangle t2(“rettangolo”, 8.0, 12.0);

cout << “Informazioni per t1:\n”; t1.showStyle(); t1.showDim(); cout << “L’area è “ << t1.area() << “\n”;

cout << “\n”; cout << “Informazioni per t2:\n”; t2.showStyle(); t2.showDim(); cout << “L’area è “ << t2.area() << “\n”;

return 0;}

Richiama il costruttoredi TwoDShape.

Page 15: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 347

Qui Triangle() richiama TwoDShape con i parametri w e h che inizializzano width eheight utilizzando questi valori. Triangle non inizializza più questi valori da sola.Deve solo inizializzare il valore specifico style. Questo lascia TwoDShape libera dicostruire il suo sottooggetto nel modo che preferisce. Inoltre TwoDShape può ag-giungere funzionalità di cui le classi derivate esistenti non sono a conoscenza.Il costruttore della classe base può richiamare qualsiasi costruttore definito dallaclasse base. Il costruttore eseguito sarà quello corrispondente al tipo degli argo-menti. Per esempio, ecco le versioni espanse delle classi TwoDShape e Triangle cheincludono ulteriori costruttori:

// Aggiunge a TwoDShape un nuovo costruttore.

#include <iostream>#include <cstring>using namespace std;

// Una classe per oggetti bidimensionali.class TwoDShape { // queste sono private double width; double height;public:

// Costruttore standard. TwoDShape() { width = height = 0.0; }

// Costruttore di TwoDShape. TwoDShape(double w, double h) { width = w; height = h; }

// Costruisce un oggetto con larghezza e altezza uguali. TwoDShape(double x) { width = height = x; }

void showDim() { cout << “La larghezza e l’altezza sono “ << width << “ e “ << height << “\n”; }

// funzioni d’accesso double getWidth() { return width; } double getHeight() { return height; } void setWidth(double w) { width = w; } void setHeight(double h) { height = h; }};

// Triangle deriva da TwoDShape.class Triangle : public TwoDShape { char style[20]; // ora è privatopublic:

I varicostruttoridi Two-DShape.

Page 16: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

348 Giorno 10

/* Un costruttore standard. Richiama automaticamente il costruttore standard di TwoDShape. */ Triangle() { strcpy(style, “sconosciuto”); }

// Costruttore con tre parametri. Triangle(char *str, double w, double h) : TwoDShape(w, h) { strcpy(style, str); }

// Costruttore di un triangolo isoscele. Triangle(double x) : TwoDShape(x) { strcpy(style, “isoscele”); }

double area() { return getWidth() * getHeight() / 2; }

void showStyle() { cout << “Il triangolo è “ << style << “\n”; }};

int main() { Triangle t1; Triangle t2(“rettangolo”, 8.0, 12.0); Triangle t3(4.0);

t1 = t2;

cout << “Informazioni per t1: \n”; t1.showStyle(); t1.showDim(); cout << “L’area è “ << t1.area() << “\n”;

cout << “\n”;

cout << “Informazioni per t2: \n”; t2.showStyle(); t2.showDim(); cout << “L’area è “ << t2.area() << “\n”;

cout << “\n”;

cout << “Informazioni per t3: \n”; t3.showStyle(); t3.showDim(); cout << “L’area è “ << t3.area() << “\n”;

cout << “\n”;

return 0;}

I varicostruttoridi Triangle.

Page 17: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 349

Ecco l’output prodotto da questa versione:

Informazioni per t1:Il triangolo è rettangoloLa larghezza e l’altezza sono 8 e 12L’area è 48

Informazioni per t2:Il triangolo è rettangoloLa larghezza e l’altezza sono 8 e 12L’area è 48

Informazioni per t3:Il triangolo è isosceleLa larghezza e l’altezza sono 4 e 4L’area è 8

Verifica

10.7 In quale modo una classe derivata esegue il costruttore della sua classe base?

10.8 È possibile passare parametri a un costruttore della classe base?

10.9 Quale costruttore è responsabile dell’inizializzazione della parte della classebase di un oggetto derivato? Quello definito dalla classe derivata o quello defi-nito dalla classe base?

Esercizio 10.1: Estensione della classe Vehicle.Questo progetto crea una sottoclasse della classe Vehicle sviluppata in precedenzanel Giorno 8. Come si ricorderà, Vehicle incapsula informazioni relative a veicoli fracui il numero di passeggeri trasportabile, la capacità del serbatoio di carburante e ilconsumo. Si può utilizzare la classe Vehicle come punto di partenza per lo sviluppodi classi più specializzate. Per esempio, un tipo di veicolo può essere il camion,Truck. Un attributo importante di un camion è la sua capacità di carico. Pertanto, percreare una classe Truck si può ereditare Vehicle aggiungendo una variabile d’istanzache conserva la capacità di carico. In questo progetto si proverà a creare la classeTruck. In questo caso le variabili d’istanza di Vehicle verranno rese private e perottenere il loro valore verranno utilizzate delle funzioni d’accesso.

Descrizione

1. Creare un file chiamato TruckDemo.cpp e copiarvi l’ultima implementazionedella classe Vehicle dal Giorno 8.

2. Creare una classe Truck nel seguente modo:

// Uso di Vehicle per creare una classe specializzata Truck (camion).class Truck : public Vehicle { int cargocap; // capacità di caricopublic:

Page 18: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

350 Giorno 10

// Questo è un costruttore per Truck. Truck(int p, int f, int m, int c) : Vehicle(p, f, m) { cargocap = c; }

// Funzione d’accesso per cargocap. int get_cargocap() { return cargocap; }};

Qui la classe Truck eredita dalla classe Vehicle aggiungendovi il membro car-gocap. Pertanto Truck includerà tutti gli attributi generali di un veicolo definitida Vehicle, cui però aggiunge un elemento specifico della sua classe.

3. Ecco il listato dell’intero programma che illustra l’utilizzo della classe Truck:

// Crea una sottoclasse di Vehicle chiamata Truck.

#include <iostream>using namespace std;

// Dichiara la classe Vehicle.class Vehicle { // Queste sono private. int passengers; // numero di passeggeri int fuelcap; // capacità serbatoio int mpg; // consumo di carburantepublic: // Questo è un costruttore per Vehicle. Vehicle(int p, int f, int m) { passengers = p; fuelcap = f; mpg = m; }

// calcola e restituisce l’autonomia. int range() { return mpg * fuelcap; }

// Funzioni d’accesso. int get_passengers() { return passengers; } int get_fuelcap() { return fuelcap; } int get_mpg() { return mpg; }};

// Uso di Vehicle per creare una classe specializzata Truck.class Truck : public Vehicle { int cargocap; // capacità di caricopublic:

// Questo è un costruttore per Truck. Truck(int p, int f, int m, int c) : Vehicle(p, f, m) { cargocap = c; }

Page 19: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 351

// Funzione d’accesso per cargocap. int get_cargocap() { return cargocap; }};

int main() {

// costruisce alcuni elementi Truck Truck semi(2, 200, 7, 44000); Truck pickup(3, 28, 15, 2000); int dist = 252;

cout << “Semi può trasportare “ << semi.get_cargocap() << “.\n”; cout << “La sua autonomia è di “ << semi.range() << “ miglia.\n”; cout << “Per fare “ << dist << “ miglia, semi ha bisogno di “ << dist / semi.get_mpg() << “ galloni di carburante.\n\n”;

cout << “Pickup può trasportare “ << pickup.get_cargocap() << “.\n”; cout << “La sua autonomia è di “ << pickup.range() << “ miglia.\n”; cout << “Per fare “ << dist << “ miglia pickup ha bisogno di “ << dist / pickup.get_mpg() << “ galloni di carburante.\n”;

return 0;}

4. Ecco l’output prodotto dal programma:

Semi può trasportare 44000.La sua autonomia è di 1400 miglia.Per fare 252 miglia, semi ha bisogno di 36 galloni di carburante.

Pickup può trasportare 2000.La sua autonomia è di 420 miglia.Per fare 252 miglia pickup ha bisogno di 16 galloni di carburante.

5. Da Vehicle possono essere derivate molte altre classi. Per esempio, la seguentestruttura crea una classe per i fuoristrada che memorizza l’altezza dal suolo delveicolo:

// Crea una classe per veicoli fuoristradaclass OffRoad : public Vehicle { int groundClearance; // altezza dal suolo del veicolopublic: // ...};

Dunque, una volta che si è creata una classe base che definisce l’aspetto gene-rale di un oggetto, tale classe base può essere ereditata per creare classi specia-lizzate. Ogni classe derivata aggiungerà i propri attributi specifici. Questa èl’essenza dell’ereditarietà.

Page 20: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

352 Giorno 10

Creazione di una gerarchia multilivelloFinora sono state utilizzate semplici gerarchie di classi costituite solo da una classebase e una classe derivata. Tuttavia è possibile costruire gerarchie che contengonoi livelli di ereditarietà desiderati. Come si è detto, è possibile utilizzare una classederivata come classe base per derivare un’altra classe. Per esempio, date tre classichiamate A, B e C, C può essere derivata da B la quale può a sua volta essere derivatada A. Quando si verifica questa situazione, ogni classe derivata eredita tutti i trattidelle sue classi base. In questo caso, C eredita tutti gli aspetti di B e A.Per vedere come funziona e come può essere utile questa gerarchia multilivello, siconsideri il seguente programma. In questo programma la classe derivata Triangleviene utilizzata come classe base per creare la classe derivata ColorTriangle laquale eredita tutte le caratteristiche di Triangle e TwoDShape, alle quali aggiunge ilcampo color che contiene il colore del triangolo.

// Una gerarchia multilivello.

#include <iostream>#include <cstring>using namespace std;

// Una classe per oggetti bidimensionali.class TwoDShape { // queste sono private double width; double height;public:

// Costruttore standard. TwoDShape() { width = height = 0.0; }

// Costruttore di TwoDShape. TwoDShape(double w, double h) { width = w; height = h; }

// Costruisce un oggetto con larghezza e altezza uguali. TwoDShape(double x) { width = height = x; }

void showDim() { cout << “La larghezza e l’altezza sono “ << width << “ e “ << height << “\n”; }

// funzioni d’accesso double getWidth() { return width; } double getHeight() { return height; } void setWidth(double w) { width = w; }

Page 21: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 353

void setHeight(double h) { height = h; }};

// Triangle deriva da TwoDShape.class Triangle : public TwoDShape { char style[20]; // ora è privatopublic:

/* Un costruttore standard. Richiama automaticamente il costruttore standard di TwoDShape. */ Triangle() { strcpy(style, “sconosciuto”); }

// Costruttore con tre parametri. Triangle(char *str, double w, double h) : TwoDShape(w, h) { strcpy(style, str); }

// Costruttore di un triangolo isoscele. Triangle(double x) : TwoDShape(x) { strcpy(style, “isoscele”); }

double area() { return getWidth() * getHeight() / 2; }

void showStyle() { cout << “Il triangolo è “ << style << “\n”; }};

// Estende Triangle.class ColorTriangle : public Triangle { char color[20];public: ColorTriangle(char *clr, char *style, double w, double h) : Triangle(style, w, h) { strcpy(color, clr); }

// Visualizza il colore. void showColor() { cout << “Il colore è “ << color << “\n”; }};

int main() { ColorTriangle t1(“Blu”, “rettangolo”, 8.0, 12.0); ColorTriangle t2(“Rosso”, “isoscele”, 2.0, 2.0);

cout << “Informazioni per t1:\n”; t1.showStyle(); t1.showDim(); t1.showColor();

Un oggetto ColorTriangle può richiamare le funzionidefinite da lui stesso e dalle sue classi base.

ColorTriangle ereditadalla classe Triangle,la quale eredita daTwoDShape.

Page 22: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

354 Giorno 10

cout << “L’area è “ << t1.area() << “\n”;

cout << “\n”;

cout << “Informazioni per t2:\n”; t2.showStyle(); t2.showDim(); t2.showColor(); cout << “L’area è “ << t2.area() << “\n”;

return 0;}

Ecco l’output prodotto dal programma:

Informazioni per t1:Il triangolo è rettangoloLa larghezza e l’altezza sono 8 e 12Il colore è BluL’area è 48

Informazioni per t2:Il triangolo è isosceleLa larghezza e l’altezza sono 2 e 2Il colore è RossoL’area è 2

Grazie all’ereditarietà, ColorTriangle può utilizzare le classi Triangle e TwoDShapeprecedentemente definite, aggiungendo solo le informazioni aggiuntive specifiche.Questa è l’importanza dell’ereditarietà: consente di riutilizzare il codice già scritto.Questo esempio illustra un altro argomento importante. In una gerarchia di classi,se un costruttore della classe base richiede dei parametri, allora tutte le classi deri-vate devono passare tali parametri a cascata. Questo è vero indipendentemente dalfatto che una classe derivata richieda dei propri parametri.

Ereditare da più classi baseIn C++ una classe derivata può ereditare contemporaneamente da due o più classibase. Per esempio, in questo breve programma, D eredita sia da B1 che da B2:

// Un esempio con più classi base.

#include <iostream>using namespace std;

class B1 {protected: int x;public: void showx() { cout << x << “\n”; }};

class B2 {protected:

Page 23: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 355

int y;public: void showy() { cout << y << “\n”; }};

// Eredita da più classi base.class D: public B1, public B2 {public: /* x e y sono accessibili poiché sono protette in B1 e B2, non sono private. */ void set(int i, int j) { x = i; y = j; }};

int main(){ D ob;

ob.set(10, 20); // fornita da D ob.showx(); // da B1 ob.showy(); // da B2

return 0;}

Come si può vedere in questo esempio, per poter ereditare da più classi base siutilizza un elenco separato da virgole. Inoltre occorre ricordarsi di utilizzare unospecificatore d’accesso per ognuna delle classi base ereditate.

Quando vengono eseguite le funzionicostruttore e distruttorePoiché una classe base, una classe derivata o entrambe possono contenere costrut-tori e/o distruttori, è importante comprendere l’ordine in cui questi vengono ese-guiti. In particolare, quando viene creato un oggetto di una classe derivata, in qualeordine vengono richiamati i costruttori? Quando poi l’oggetto termina la propriaesistenza, in quale ordine vengono richiamati i distruttori? Per rispondere a questedomande, si osservi il seguente programma:

#include <iostream>using namespace std;

class B {public: B() { cout << “Costruzione della porzione base\n”; } ~B() { cout << “Distruzione della porzione base\n”; }};

class D: public B {public: D() { cout << “Costruzione della porzione derivata\n”; } ~D() { cout << “Distruzione della porzione derivata\n”; }};

Qui D eredita siada B1 che da B2.

Page 24: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

356 Giorno 10

int main(){ D ob;

// non fa nulla ma costruisce e distrugge ob

return 0;}

Come si può vedere dal commento in main(), questo programma non fa altro checostruire e poi distruggere un oggetto chiamato ob appartenente alla classe D. Ilprogramma produce il seguente output:

Costruzione della porzione baseCostruzione della porzione derivataDistruzione della porzione derivataDistruzione della porzione base

Come si può vedere dall’output, prima viene eseguito il costruttore di B, seguito dalcostruttore di D. Poi (dato che in questo programma ob viene distrutto immediata-mente), viene richiamato il costruttore di D, seguito da quello di B.I risultati di questo esperimento possono essere generalizzati nel seguente modo:quando viene creato un oggetto di una classe derivata, per primo viene richiamatoil costruttore della classe base seguito dal costruttore della classe derivata. Quandopoi l’oggetto viene distrutto, per primo viene richiamato il suo distruttore seguito daquello della classe base. In altre parole i costruttori vengono eseguiti nell’ordine diderivazione e i distruttori nell’ordine inverso.

Domande e risposte

D. Perché i costruttori vengono richiamati secondo l’ordine di derivazionee i distruttori in ordine inverso?

R. Se si prova a riflettere, è logico che i costruttori vengano eseguiti nell’ordine diderivazione. Poiché una classe base non conosce le sue classi derivate, ogniinizializzazione che deve eseguire sarà distinta e talvolta anche un prerequisitoper l’inizializzazione eseguita dalla classe derivata. Pertanto per primo deveessere richiamato il costruttore della classe base. Analogamente è abbastanzaovvio che i distruttori vengano eseguiti in ordine inverso rispetto alla derivazio-ne. Poiché la classe base contiene la classe derivata, la distruzione della classebase implica la distruzione della classe derivata. Pertanto il distruttore dellaclasse derivata deve essere richiamato prima che l’oggetto venga completamen-te distrutto.

Nel caso di una gerarchia di classi multilivello (ovvero dove una classe derivatadiviene classe base per un’altra classe derivata) si applica questa stessa regola: icostruttori vengono richiamati secondo l’ordine di derivazione e i distruttori in ordi-ne inverso. Quando una classe eredita da più classi base, i costruttori vengonorichiamati da sinistra a destra in base all’ordine in cui sono specificati. I distruttorivengano richiamati in ordine inverso, da destra a sinistra.

Page 25: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 357

Verifica

10.10 Si può usare una classe derivata come classe base di un’altra classe derivata?

10.11 In una gerarchia di classi, in quale ordine vengono richiamati i costruttori?

10.12 In una gerarchia di classi, in quale ordine vengono richiamati i distruttori?

Puntatori a tipi derivatiPrima di parlare delle funzioni virtuali e del polimorfismo, è necessario accennarea un aspetto importante dei puntatori. I puntatori alle classi base e alle classi deri-vate sono correlati in un modo particolare. In generale, un puntatore di un tipo nonpuò puntare a un oggetto di un altro tipo. L’eccezione a questa regola è rappresen-tata dai puntatori alla classe base e agli oggetti derivati. In C++ un puntatore allaclasse base può essere utilizzato anche per puntare a un oggetto di una qualsiasiclasse derivata da tale classe base. Per esempio, supponendo che vi sia una classebase chiamata B e che D sia una classe derivata da B, ogni puntatore dichiarato comepuntatore a B può essere utilizzato anche per puntare a oggetti di tipo D. Pertanto,date le seguenti righe di codice:

B *p; // puntatore all’oggetto di tipo BB B_ob; // oggetto di tipo BD D_ob; // oggetto di tipo D

entrambe le seguenti istruzioni sono perfettamente valide:

p = &B_ob; // p punta all’oggetto di tipo Bp = &D_ob; /* p punta all’oggetto di tipo D, che è un oggetto derivato da B. */

Un puntatore base può essere utilizzato per accedere solo a quelle parti di unoggetto derivato che sono state ereditate dalla classe base. Pertanto, in questoesempio, p può essere utilizzato per accedere a tutti gli elementi di D_ob ereditati daB_ob. Gli elementi specifici di D_ob non potranno però essere impiegati tramite p (ameno che venga eseguita una conversione di tipo).Un altro elemento da comprendere è che sebbene un puntatore alla classe basepossa essere utilizzato per puntare a un oggetto derivato, non vale il contrario.Pertanto non si può accedere a un oggetto della classe base utilizzando un puntato-re o a una classe derivata.Come si sa, un puntatore viene incrementato e decrementato rispetto al tipo cuipunta. Pertanto, quando un puntatore alla classe base punta a un oggetto derivato,incrementandolo o decrementandolo non ci si troverà nel punto in cui inizia l’og-getto successivo della classe derivata a quello che il puntatore ritiene essere ilprossimo oggetto della classe base. Pertanto, quando un puntatore alla classe baseviene utilizzato per puntare a un oggetto derivato, si deve evitare di impiegare leoperazioni di incremento o decremento.

Page 26: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

358 Giorno 10

Il fatto che un puntatore a una classe base possa essere utilizzato per puntare a unoggetto derivato da tale classe base è estremamente importante e anzi fondamenta-le in C++. Come si vedrà fra poco, questa flessibilità è cruciale per il modo in cui illinguaggio C++ implementa il polimorfismo runtime.

Riferimenti a tipi derivatiCome si è già visto per i puntatori, l’indirizzo della classe base può essere utilizzatoper far riferimento a un oggetto di un tipo derivato. L’applicazione più tipica di ciòsi trova nei parametri di funzione. Un parametro indirizzo di una classe base puòricevere oggetti della classe base e di ogni altro tipo derivato da tale classe base.

Funzioni virtuali e polimorfismoIl supporto del polimorfismo del linguaggio C++ si basa sull’ereditarietà e sui pun-tatori alla classe base. La funzionalità che implementa il polimorfismo è rappresen-tata dalle funzioni virtuali. La parte rimanente di questo capitolo esamina questaimportante funzionalità.

Elementi di base delle funzioni virtualiUna funzione virtuale è una funzione dichiarata come virtuale in una classe base edefinita in uno o più classi derivate. Pertanto, ogni classe derivata potrà avere unapropria versione di una funzione virtuale. Ciò che rende così interessanti le funzio-ni virtuali è ciò che accade quando per richiamarne una viene utilizzato un punta-tore alla classe base. Quando una funzione virtuale viene richiamata tramite unpuntatore alla classe base, il linguaggio C++ determina quale versione di tale fun-zione richiamare sulla base del tipo dell’oggetto puntato dal puntatore. Questascelta viene eseguita runtime. Pertanto, quando si punta a oggetti differenti, verran-no eseguite versioni differenti della funzione virtuale. In altre parole, è il tipodell’oggetto puntato (non il tipo del puntatore) che determina la versione dellafunzione virtuale che verrà eseguita. Pertanto, se una classe base contiene unafunzione virtuale e se da tale classe base vengono derivate due o più classi, allora,quando un puntatore alla classe base punterà a tipi differenti di oggetti, verrannoeseguite versioni differenti della funzione virtuale.Per dichiarare una funzione come virtuale in una classe base si deve far precederealla sua dichiarazione la parola riservata virtual. Quando una funzione virtualeviene ridefinita da una classe derivata, la parola riservata virtual non deve essereripetuta (anche se ripetendola non si commette alcun errore).Una classe che include una funzione virtuale è chiamata classe polimorfica. Questotermine si applica anche a una classe che eredita da una classe base contenenteuna funzione virtuale.

Page 27: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 359

Il seguente programma illustra l’uso delle funzioni virtuali:

// Un breve esempio d’uso delle funzioni virtuali.

#include <iostream>using namespace std;

class B {public: virtual void who() { // specifica una funzione virtuale cout << “Base\n”; }};

class D1 : public B {public: void who() { // ridefinsce who() per D1 cout << “Prima derivazione\n”; }};

class D2 : public B {public: void who() { // ridefinisce who() per D2 cout << “Seconda derivazione \n”; }};

int main(){ B base_obj; B *p; D1 D1_obj; D2 D2_obj;

p = &base_obj; p->who(); // accesso a who di B

p = &D1_obj; p->who(); // accesso a who di D1

p = &D2_obj; p->who(); // accesso a who di D2

return 0;}

Questo programma produce il seguente output:

BasePrima derivazioneSeconda derivazione

Per capire ciò che accade verrà esaminato in dettaglio il programma. Come si puòvedere, in B la funzione who() viene dichiarata come virtuale. Questo significa chela funzione può essere ridefinita da una classe derivata. All’interno di D1 e D2, who()viene ridefinita per la rispettiva classe. All’interno di main() vengono dichiarate

Dichiara una funzione virtuale.

Ridefinisce lafunzione virtualeper D1.

Ridefinisce lafunzione virtualeuna seconda voltaper D2.

Richiama la funzio-ne virtuale tramiteun puntatore allaclasse base.

Page 28: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

360 Giorno 10

quattro variabili: base_obj che c’è un oggetto di tipo B; p che è un puntatore aoggetti di tipo B; D1_obj e D2_obj che sono oggetti delle due classi derivate. Poi a pviene assegnato l’indirizzo di base_obj e viene richiamata la funzione who(). Poichéwho() è dichiarata come virtuale, il linguaggio C++ determina runtime quale versio-ne di who() eseguire sulla base del tipo dell’oggetto puntato da p. In questo caso ppunta a un oggetto di tipo B e dunque viene eseguita la versione di who() dichiaratain B. Poi a p viene assegnato l’indirizzo di D1_obj. Questo è un puntatore alla classebase che può far riferimento a una classe derivata. Ora, quando viene richiamatawho(), il linguaggio C++ controlla ancora una volta quale tipo di oggetto vienepuntato da p che, sulla base di tale tipo, determina quale versione di who() richia-mare. Poiché p punta a un oggetto di tipo D1, verrà utilizzata tale versione di who().Analogamente, quando a p viene assegnato l’indirizzo di D2_obj, verrà eseguita laversione di who() dichiarata all’interno di D2.Per ricapitolare, quando una funzione virtuale viene richiamata tramite un puntato-re alla classe base, la versione della funzione effettivamente eseguita viene deter-minata runtime in base al tipo di oggetto puntato.Sebbene le funzioni virtuali vengano normalmente richiamate tramite puntatori allaclasse base, possono anche essere richiamate utilizzando la normale sintassi conl’operatore punto. Questo significa che nell’esempio precedente sarebbe stato pos-sibile accedere a who() utilizzando la seguente istruzione:

D1_obj.who();

Tuttavia questa istruzione ignora il fatto che la funzione virtuale è polimorfica. Ilpolimorfismo si realizza solo quando si accede a una funzione virtuale tramite unpuntatore o un indirizzo alla classe base, ovvero runtime.A prima vista, la ridefinizione di una funzione virtuale in una classe derivata sembrauna forma particolare di overloading di funzioni ma non è così. In realtà questi dueprocessi sono fondamentalmente differenti. Innanzitutto, una funzione in overloa-ding deve differire per il tipo e/o il numero dei parametri mentre una funzionevirtuale ridefinita deve avere esattamente lo stesso tipo e numero di parametri. Inpratica il prototipo di una funzione virtuale e delle sue ridefinizioni deve essereesattamente lo stesso. Se vi sono differenze nei prototipi, la funzione verrà conside-rata come overloading e se ne perderà la natura virtuale. Un’altra restrizione è ilfatto che una funzione virtuale deve essere un membro e non friend della classeper la quale è definita. Nulla però impedisce che una funzione virtuale possa esserefriend di un’altra classe. Inoltre i distruttori (ma non i costruttori) possono esserevirtuali.

Ereditare le funzioni virtualiUna volta che una funzione è dichiarata come virtuale, rimane virtuale indipenden-temente dal numero di livelli di derivazione che deve attraversare. Per esempio, seD2 viene derivata da D1 invece che da B, come indicato dal seguente esempio, who()sarà comunque virtuale:

// Deriva da D1, non da B.class D2 : public D1 {

Page 29: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 361

public: void who() { // definisce who() cout << “Seconda derivazione\n”; }};

Quando una classe derivata non modifica una funzione virtuale, allora verrà utiliz-zata la funzione così come è stata definita nella sua classe base. Per esempio, siprovi questa versione del programma precedente in cui D2 non ridefinisce who():

#include <iostream>using namespace std;

class B {public: virtual void who() { cout << “Base\n”; }};

class D1 : public B {public: void who() { cout << “Prima derivazione\n”; }};

class D2 : public B {// who() non è definita};

int main(){ B base_obj; B *p; D1 D1_obj; D2 D2_obj;

p = &base_obj; p->who(); // accesso a who() da B

p = &D1_obj; p->who(); // accesso a who() da D1

p = &D2_obj; p->who(); /* accesso a who() da B poiché D2 non la ridefinisce */

return 0;}

Il programma produce il seguente output:

BasePrima derivazioneBase

D2 non ridefini-sce who().

Richiama lafunzione who()definita da B.

Page 30: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

362 Giorno 10

Poiché D2 non modifica who(), verrà utilizzata la versione di who() definita in B.Si deve tenere in considerazione che le caratteristiche ereditate con virtual sonogerarchiche. Pertanto, se il precedente esempio venisse modificato in modo che D2derivasse da D1 invece che da B, allora quando who() verrà richiamata su unooggetto di tipo D2, non verrà richiamata la versione di who() contenuta in B maquella dichiarata all’interno di D1 che ora rappresenta la classe più vicina a D2.

Utilità delle funzioni virtualiCome si è detto in precedenza, le funzioni virtuali in combinazione con i tipiderivati consentono al C++ di supportare il polimorfismo runtime. Il polimorfismo èfondamentale nella programmazione a oggetti poiché consente a una classe gene-ralizzata di specificare quelle funzioni che saranno comuni a tutte le classi derivateconsentendo nel contempo a una classe derivata di definire la specifica implemen-tazione di alcune o di tutte queste funzioni. Talvolta l’idea è espressa nel seguentemodo: la classe base stabilisce l’interfaccia generale che deve essere condivisa daogni oggetto derivato da tale classe ma consente alla classe derivata di definire unmetodo effettivo di implementazione di tale interfaccia. Questo è il motivo per cuiper descrivere il polimorfismo viene frequentemente utilizzata la frase “un’interfac-cia, più metodi”.Per applicare con successo il polimorfismo occorre comprendere che le classi basee derivata formano una gerarchia che va da una maggiore a una minore generaliz-zazione (dalla classe base alla classe derivata). Se progettata correttamente, la clas-se base fornisce tutti gli elementi utilizzabili direttamente da una classe derivata.Inoltre definisce quelle funzioni che la classe derivata deve implementare. Questolascia alla classe derivata la flessibilità di definire i propri metodi e nel contempocrea un’interfaccia uniforme. Pertanto, dato che la forma dell’interfaccia è definitadalla classe base, tutte le classi derivate devono condividere la stessa interfacciacomune. Quindi l’uso delle funzioni virtuali consente alla classe base di definire leinterfacce generiche che verranno utilizzate da tutte le classi derivate.A questo punto ci si potrebbe chiedere perché è importante utilizzare un’interfacciauniforme con più implementazioni. La risposta rimanda alle tecniche di program-mazione a oggetti, che hanno lo scopo di aiutare il programmatore a gestire pro-grammi sempre più complessi. Per esempio, se si sviluppa correttamente il pro-gramma, si saprà che tutti gli oggetti derivati da una classe base potranno essereimpiegati nello stesso modo generale, anche se le specifiche azioni possono variareda una classe derivata all’altra. Questo significa che occorre preoccuparsi di unasola interfaccia e non di più interfacce. Inoltre, la classe derivata è libera di utilizza-re qualsiasi funzionalità fornita dalla classe base senza costringere il programmato-re a reinventare tali elementi.La separazione dell’interfaccia e dell’implementazione consente anche di crearedelle librerie di classi che possono essere fornite anche ad altri sviluppatori. Sequeste librerie sono implementate correttamente, forniranno un’interfaccia comuneutilizzabile per derivare nuove classi che rispondono a esigenze specifiche. Peresempio, sia MFC (Microsoft Foundation Classes) che .NET Framework supportano

Page 31: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 363

la programmazione Windows. Utilizzando queste classi, il programma potrà eredi-tare molte delle funzionalità che devono essere necessariamente presenti in unprogramma Windows. Basterà aggiungere le funzionalità specifiche dell’applicazio-ne. Questo è un grande vantaggio per la realizzazione di sistemi complessi.

Utilizzo delle funzioni virtualiPer comprendere meglio l’utilità delle funzioni virtuali, si proverà ad applicarle allaclasse TwoDShape. Negli esempi precedenti, ogni classe derivata da TwoDShape defi-nisce una funzione chiamata area(). Questo suggerisce il fatto che sarebbe megliorendere area() una funzione virtuale della classe TwoDShape consentendo a ogniclasse derivata di modificarla adattandola alla forma geometrica specifica incapsu-lata dalla classe. Questo è lo scopo del prossimo programma. Per comodità allaclasse TwoDShape viene aggiunto anche un campo per il nome (che ha lo scopo disemplificare l’utilizzo delle classi).

// Funzioni virtuali e polimorfismo.

#include <iostream>#include <cstring>using namespace std;

// Una classe per oggetti bidimensionali.class TwoDShape { // queste sono private double width; double height;

// aggiunge il nome di un campo char name[20];public:

// Costruttore standard. TwoDShape() { width = height = 0.0; strcpy(name, “sconosciuto”); }

// Costruttore di TwoDShape. TwoDShape(double w, double h, char *n) { width = w; height = h; strcpy(name, n); }

// Costruisce un oggetto con larghezza e altezza uguali. TwoDShape(double x, char *n) { width = height = x; strcpy(name, n); }

void showDim() { cout << “La larghezza e l’altezza sono “ <<

Page 32: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

364 Giorno 10

width << “ e “ << height << “\n”; }

// funzioni d’accesso double getWidth() { return width; } double getHeight() { return height; } void setWidth(double w) { width = w; } void setHeight(double h) { height = h; } char *getName() { return name; }

// Aggiunge area() a TwoDShape e la rende virtuale. virtual double area() { cout << “Errore: area() deve essere modificata.\n”; return 0.0; }

};

// Triangle deriva da TwoDShape.class Triangle : public TwoDShape { char style[20]; // ora è privatopublic:

/* Un costruttore standard. Richiama automaticamente il costruttore standard di TwoDShape. */ Triangle() { strcpy(style, “sconosciuto”); }

// Costruttore con tre parametri. Triangle(char *str, double w, double h) : TwoDShape(w, h, “triangolo”) { strcpy(style, str); }

// Costruttore di un triangolo isoscele. Triangle(double x) : TwoDShape(x, “triangolo”) { strcpy(style, “isoscele”); }

// Modifica la funzione area() dichiarata in TwoDShape. double area() { return getWidth() * getHeight() / 2; }

void showStyle() { cout << “Il triangolo è “ << style << “\n”; }};

// Una classe derivata di TwoDShape per i rettangoli.class Rectangle : public TwoDShape {public:

// Costruisce un rettangolo. Rectangle(double w, double h) : TwoDShape(w, h, “rettangolo”) { }

La funzione area()ora è virtuale.

Modifica la funzionearea() in Triangle.

Page 33: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 365

// Costruisce un quadrato. Rectangle(double x) : TwoDShape(x, “rettangolo”) { }

bool isSquare() { if(getWidth() == getHeight()) return true; return false; }

// Questa è un’altra modifica di area(). double area() { return getWidth() * getHeight(); }};

int main() { // dichiara un array di puntatori a oggetti TwoDShape. TwoDShape *shapes[5];

shapes[0] = &Triangle(“rettangolo”, 8.0, 12.0); shapes[1] = &Rectangle(10); shapes[2] = &Rectangle(10, 4); shapes[3] = &Triangle(7.0); shapes[4] = &TwoDShape(10, 20, “generico”);

for(int i=0; i < 5; i++) { cout << “l’oggetto è “ << shapes[i]->getName() << “\n”;

cout << “L’area è “ << shapes[i]->area() << “\n”;

cout << “\n”; }

return 0;}

Ecco l’output prodotto dal programma:

l’oggetto è triangoloL’area è 48

l’oggetto è rettangoloL’area è 100

l’oggetto è rettangoloL’area è 40

l’oggetto è triangoloL’area è 24.5

l’oggetto è genericoErrore: area() deve essere modificata.L’area è 0

Si esamini attentamente il programma. Innanzitutto area() viene dichiarata comevirtuale nella classe TwoDShape e poi viene adattata sia in Triangle che in Rectan-

Modifica la funzionearea() anche perRectangle.

Viene richiamata laversione di area()corretta per ciascunoggetto.

Page 34: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

366 Giorno 10

gle. All’interno di TwoDShape, area() ha una semplice implementazione standardche ha il solo scopo di informare del fatto che questa funzione deve essere adattatadalle classi derivate. Dunque ogni classe fornirà un’implementazione di area()adatta per il proprio tipo di oggetto. Pertanto, se si dovesse implementare unaclasse per un’ellisse, area() dovrebbe calcolare l’area dell’ellisse.Il programma precedente mostra anche un’altra importante funzionalità. Si noti chein main(), shapes viene dichiarata come un array di puntatori a oggetti TwoDShape.Tuttavia gli elementi di questi array sono puntatori assegnati a oggetti Triangle,Rectangle e TwoDShape. L’operazione è consentita poiché un puntatore a una classebase può puntare a un oggetto di una classe derivata. Quindi il programma esegueun ciclo all’interno dell’array, visualizzando informazioni su ciascun oggetto. Anchese il programma è molto semplice, illustra la potenza dell’ereditarietà e delle fun-zioni virtuali. Il tipo dell’oggetto puntato da un puntatore alla classe base vienedeterminato runtime e pertanto si utilizza sempre l’azione appropriata. Se un ogget-to è derivato da TwoDShape, la sua area può essere ottenuta richiamando area().L’interfaccia di questa operazione è la stessa indipendentemente dal tipo di formageometrica utilizzata.

Verifica

10.13 Che cos’è una funzione virtuale?

10.14 Perché le funzioni virtuali sono importanti?

10.15 Quando una funzione virtuale adattata viene richiamata tramite un puntatorealla classe base, quale versione della funzione viene eseguita?

Funzioni virtuali pure e classi astratteTalvolta si vuole creare una classe base che definisce solo una forma generalizzatache verrà condivisa da tutte le classi derivate, lasciando alle classi derivate il com-pito di specificare tutti i dettagli. Una classe di questo tipo determina la natura dellefunzioni che devono essere implementate dalle classi derivate ma non forniscealcuna implementazione per una o più di queste funzioni. Questa situazione puòverificarsi quando una classe base non può creare un’implementazione significativadi una funzione. Questo è il caso della versione di TwoDShape utilizzata nell’esem-pio precedente. La definizione di area() è fittizia in quanto non può calcolare névisualizzare l’area di nessun tipo di oggetto.Come si vedrà quando ci si troverà a creare delle librerie di classi, spesso unafunzione non ha alcuna definizione significativa nel contesto della classe base. Sipuò gestire questa situazione in due modi. Come si può vedere nell’esempio prece-dente si può semplicemente implementare nella classe base la visualizzazione diun messaggio. Anche se questo approccio può essere utile in alcune situazioni, peresempio in fase di debugging, in genere non è appropriato per le normali applica-zioni. Vi possono essere funzioni che devono essere ridefinite dalla classe derivata

Page 35: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 367

in modo da assegnare loro un vero significato. Si consideri la classe Triangle. Nonavrebbe alcun significato se non venisse definita area(). In questo caso si vuolegarantire che una classe derivata definisca tutte le funzioni necessarie. La soluzionedel linguaggio C++ a questo problema è rappresentata dalle funzioni virtuali pure.Una funzione virtuale pura è una funzione dichiarata in una classe base senza peròalcuna definizione. Pertanto tutte le classi derivate dovranno necessariamente defini-re una propria versione della funzione e non potranno utilizzare la versione dellaclasse base (in quanto non è stata definita). Per dichiarare una funzione virtuale purasi utilizza la seguente forma generale:

virtual tipo nome-funzione(elenco-parametri) = 0;

Qui tipo è il tipo restituito dalla funzione e nome-funzione è il nome della funzione.Utilizzando una funzione virtuale pura si può migliorare la classe TwoDShape. Poi-ché non ha alcun senso calcolare un’area per una figura bidimensionale indefinita,la seguente versione del programma dichiara area() come una funzione virtualepura di TwoDShape. Questo, naturalmente, significa che tutte le classi derivate daTwoDShape dovranno necessariamente ridefinire area().

// Uso di una funzione virtuale pura.

#include <iostream>#include <cstring>using namespace std;

// Una classe per oggetti bidimensionali.class TwoDShape { // queste sono private double width; double height;

// aggiunge il nome di un campo char name[20];public:

// Costruttore standard. TwoDShape() { width = height = 0.0; strcpy(name, “sconosciuto”); }

// Costruttore di TwoDShape. TwoDShape(double w, double h, char *n) { width = w; height = h; strcpy(name, n); }

// Costruisce un oggetto con larghezza e altezza uguali. TwoDShape(double x, char *n) { width = height = x; strcpy(name, n); }

Page 36: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

368 Giorno 10

void showDim() { cout << “La larghezza e l’altezza sono “ << width << “ e “ << height << “\n”; }

// funzioni d’accesso double getWidth() { return width; } double getHeight() { return height; } void setWidth(double w) { width = w; } void setHeight(double h) { height = h; } char *getName() { return name; }

// area() ora è una funzione virtuale pura virtual double area() = 0;

};

// Triangle deriva da TwoDShape.class Triangle : public TwoDShape { char style[20]; // ora è privatopublic:

/* Un costruttore standard. Richiama automaticamente il costruttore standard di TwoDShape. */ Triangle() { strcpy(style, “sconosciuto”); }

// Costruttore con tre parametri. Triangle(char *str, double w, double h) : TwoDShape(w, h, “triangolo”) { strcpy(style, str); }

// Costruttore di un triangolo isoscele. Triangle(double x) : TwoDShape(x, “triangolo”) { strcpy(style, “isoscele”); }

// Modifica area() dichiarata in TwoDShape. double area() { return getWidth() * getHeight() / 2; }

void showStyle() { cout << “Il triangolo è “ << style << “\n”; }};

// Una classe derivata di TwoDShape per i rettangoli.class Rectangle : public TwoDShape {public:

// Costruisce un rettangolo. Rectangle(double w, double h) : TwoDShape(w, h, “rettangolo”) { }

Ora area() è unafunzione virtuale pura.

Page 37: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 369

// Costruisce un quadrato. Rectangle(double x) : TwoDShape(x, “rettangolo”) { }

bool isSquare() { if(getWidth() == getHeight()) return true; return false; }

// Modifica area(). double area() { return getWidth() * getHeight(); }};

int main() { // dichiara un array di puntatori a oggetti TwoDShape. TwoDShape *shapes[4];

shapes[0] = &Triangle(“rettangolo”, 8.0, 12.0); shapes[1] = &Rectangle(10); shapes[2] = &Rectangle(10, 4); shapes[3] = &Triangle(7.0);

for(int i=0; i < 4; i++) { cout << “l’oggetto è “ << shapes[i]->getName() << “\n”;

cout << “L’area è “ << shapes[i]->area() << “\n”;

cout << “\n”; }

return 0;}

Se una classe contiene almeno una funzione virtuale pura, tale classe si dice astrat-ta. Una classe astratta ha un’importante caratteristica: non possono esistere oggettidi tale classe. Per dimostrarlo, si provi a eliminare la ridefinizione di area() dallaclasse Triangle del programma precedente. Quando si tenterà di creare un’istanzadi Triangle si riceverà un errore. Una classe astratta deve essere utilizzata solocome classe base per la derivazione di altre classi. Il motivo per cui una classeastratta non può essere utilizzata per dichiarare un oggetto è che una o più dellesue funzioni non sono definite. Per questo motivo, l’array shapes del programmaprecedente è stato abbreviato a quattro elementi e non viene più creato un oggettogenerico TwoDShape. Come si può vedere nel programma, anche se la classe base èastratta, è comunque possibile utilizzarla per dichiarare un puntatore che verràutilizzato per puntare agli oggetti appartenenti alle classi derivate.

Page 38: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

370 Giorno 10

Domande1. Una classe ereditata si chiama classe __________. La classe che eredita si chia-

ma classe __________.

2. Una classe base ha accesso ai membri di una classe derivata? Una classe deriva-ta ha accesso ai membri di una classe base?

3. Creare una classe derivata di TwoDShape chiamata Circle. Includere una funzio-ne area() che calcola l’area del cerchio.

4. Come si evita che una classe derivata possa avere accesso a un membro di unaclasse base?

5. Mostrare la forma generale di un costruttore che richiama un costruttore dellaclasse base.

6. Data la seguente gerarchia:

class Alpha { ...

class Beta : public Alpha { ...

Class Gamma : public Beta { ...

in quale ordine vengono richiamati i costruttori di queste classi quando vieneistanziato un oggetto Gamma?

7. Come si accede ai membri protected?

8. Un puntatore alla classe base può fare riferimento a un oggetto di una classederivata. Spiegare perché questo è importante e le sue relazioni con la ridefini-zione delle funzioni.

9. Che cos’è una funzione virtuale pura? Che cos’è una classe astratta?

10. È possibile istanziare un oggetto di una classe astratta?

11. Spiegare il modo in cui una funzione virtuale pura aiuta a implementare l’aspet-to “un interfaccia, più metodi” del polimorfismo.

Risposte alle verifiche10.1 Una classe base viene specificata dopo il nome della classe derivata separata

dal segno “:”.10.2 Sì, una classe derivata include i membri della sua classe base.10.3 No, una classe derivata non ha accesso ai membri privati della sua classe

base.

10.4 Vero, quando una classe base viene ereditata con private, i membri pub-blici della classe base divengono membri privati della classe derivata.

10.5 No, un membro privato è sempre privato della sua classe.

10.6 Per fare in modo che un membro di una classe risulti accessibile nella gerar-chia di classi ma inaccessibile all’esterno si usa lo specificatore d’accessoprotected.

Page 39: Giorno 10 Ereditarietà, funzioni virtuali e polimorfismo · Ereditarietà, funzioni virtuali e polimorfismo 337 height in TwoDShape vengono resi privati come nel seguente esempio,

Ereditarietà, funzioni virtuali e polimorfismo 371

10.7 Una classe derivata fa riferimento al costruttore della classe base semplice-mente con la clausola del suo costruttore.

10.8 Sì, al costruttore della classe base è possibile passare dei parametri.

10.9 Il costruttore responsabile dell’inizializzazione della porzione della classebase di un oggetto derivato è quello definito dalla classe base.

10.10 Sì, una classe derivata può essere utilizzata come classe base per un’altraclasse derivata.

10.11 I costruttori vengono richiamati nell’ordine di derivazione.

10.12 I distruttori vengono richiamati in ordine inverso rispetto alla derivazione.

10.13 Una funzione virtuale è una funzione dichiarata con la parola riservata vir-tual in una classe base e poi adattata in una classe derivata.

10.14 Le funzioni virtuali offrono il supporto del polimorfismo.

10.15 La versione della funzione virtuale che deve essere eseguita dipende dal tipodell’oggetto puntato al momento della chiamata. Pertanto questa valutazioneviene eseguita runtime.