Ereditarietà: classi derivate
Con la programmazione a oggetti è possibile specializzare il comportamento di una classe base creando una classe derivata. Una classe derivata si def,rnisce indicando il nome della classe di origine.
Esempio:
Visibilità delle classi derivate
Il C++ permette ad una classe derivata di:
● ereditare i dati della classe di origine;
● ereditare o modificare i metodi della classe di origine;
● aggiungere nuovi metodi e dati.
La definizione di una classe derivata implica anche la definizione della
visibilità dei suoi oggetti, in relazione al tipo di specificatore di accesso.
Tipo di accesso | public | protected | private |
Classe base | Si | Si | Si |
Classe derivata | Si | Si | No |
Altri | Si | No | No |
dove per altri s'intende qualunque elemento
esterno alla classe: una funzione, un'altra classe ecc.
Costruttori nelle classi derivate
Quando si istanzia un oggetto di una classe derivata viene chiamato prima
il costruttore di default della classe base.
Se è necessario chiamare il costruttore con parametri della classe base,
la derivata deve farlo prima del proprio costruttore.
Questa operazione è possibile inserendo la chiamata nella lista
di inizializzazione che segue l'intestazione del costruttore della
classe derivata, ma che precede il suo corpo. La lista di inizializzazione
comincia dopo il carattere ':' e termina prima della
parentesi graffa aperta '{' del corpo del costruttore.
Distruttori nelle classi derivate
Gli oggetti, sono costruiti con modalità bottom-up; di conseguenza i costruttori delle classi base sono eseguiti prima di quelli delle relative classi derivate. Al contrario, la distruzione delle classi avviene in ordine inverso (top-down).
Una classe derivata pubblicamente è in concreto un sottotipo della classe
base.
Un puntatore alla classe base può puntare a oggetti della derivata, ma non
viceversa.
Polimorfismo e funzioni virtuali
Il polimorfismo consente di generalizzare il comportamento degli oggetti
appartenenti a una gerarchia di classi e, che per questo, hanno caratteristiche
simili.
Grazie al polimorfismo è possibile scrivere programmi facilmente modificabili
ed estensibili.
Per ottenere questo comportamento è necessario creare delle classi polimorfe.
Una classe è polimorfa se possiede dei metodi
virtuali.
Per dichiarare virtuale una member function occorre farla precedere dalla
keyword virtual nella dichiarazione di classe:
class Base {
public:
Base() { dato = 3; };
virtual void Stampa() {};
protected:
int dato;
};
class Derivata : public Base {
public:
void Stampa()
{
cout << dato * dato * dato; };
};
Stampa() è un metodo polimorfo della classe Base ridefinito nella classe Derivata.
Polimorfismo e puntatori
Definendo un metodo virtual, e chiamandolo mediante un puntatore di tipo classe Base, ma inizializzato con l'indirizzo di un oggetto di una classe Derivata, sarà eseguito l'omonimo metodo della classe derivata.
Questo meccanismo consente di scrivere programmi in cui uno stesso oggetto che invoca il medesimo metodo può avere comportamenti differenti durante il runtime, secondo lo stato del programma.
Il frammento di codice:
Base *pB = new Derivata(); // Esegue Stampa
di Derivata
pB->Stampa();
Late binding
È il meccanismo mediante il quale funziona il polimorfismo.
Quando un puntatore, di tipo classe base, viene impiegato per chiamare una
funzione virtuale, in effetti viene puntato un oggetto della classe derivata.
Questa classe possiede un data member nascosto che a sua volta punta a una
tabella contenente gli indirizzi delle funzioni virtuali che ha implementato.
In questo modo sarà richiamato il metodo omonimo alla funzione virtuale
chiamata.
Classi astratte
Le classi astratte servono a rappresentare concetti che saranno implementati nelle classi derivate. Una classe è astratta se contiene almeno una funzione virtuale pura:
virtual void Stampa() = 0;
Non è possibile istanziare oggetti di una classe
astratta.
Le classi derivate da una classe astratta devono definire tutte le funzioni
virtuali pure altrimenti saranno a loro volta considerate classi astratte.
Si possono definire puntatori a una classe base astratta e usarli polimorficamente
per puntare a oggetti delle classi derivate.
Ereditarietà multipla
L'ereditarieta multipla permette di derivare una classe da due o più classi
base.
La sintassi dell'ereditarietà semplice viene estesa per permettere una lista
di classi base:
class A {
...
};
class B {
...
};
class AB: public A, private B {
...
};
Classi composte
Sono classi che aggregano o contengono altre classi.
L'aggregazione si effettua seguendo questi passi:
1 si crea l'oggetto da aggregare;
2 si definisce un suo puntatore o reference;
3 si costruisce l'oggetto contenitore passandogli il puntatore o il reference dell'oggetto da aggregare.
Esempio :
class Aggregato {...};
class Contenitore {
Contenitore(Aggregato *ptr) ;
Aggregato *p;
};
Aggregato *a = new Aggregato(1.2);
Contenitore c(a);
La costruzione di una classe che contiene un'altra classe è invece più semplice; occorre:
1 aggiungere un data member di classe componente alla classe contenitore;
2 implementare il costruttore della classe contenitore in modo che richiami prima quello della classe componente.