edutecnica

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.