Polimorfismo

Nel caso in cui l'ereditarietà venga usata insieme ai puntatori le classi possono comportarsi in modo ambiguo. In che senso ambiguo?

L'ereditarietà stabilisce che:

  • un oggetto di tipo classe Derivata deve essere (allo stesso tempo) anche del tipo Base
    • cioè che i puntatori (o reference) che uso per oggetti della Base possano puntare anche oggetti della Derivata

Perché questo è possibile, mentre non è possibile l'inverso?

Nel seguente esempio la classe Derivata è “derivata” da una classe Base (la classe Base è contenuta nella Derivata).

class Base
{....};

class Derivata : public Base 
{....};

Si può creare pBase e pDeriv, rispettivamente, un puntatore ad un oggetto di Base e ad un oggetto di Derivata. Si nota che i due puntatori puntano a due aree di dimensione diversa (e colore diverso).

Se si immagina di scambiare il contenuto delle aree puntate dai puntatori, ad una prima impressione potrebbe sembrare che lo scambio sarebbe corretto solo se l'area puntata (tratteggiata) fosse abbastanza grande per contenere il nuovo oggetto. Invece le cose stanno esattamente al contrario…

  1. Per utilizzare il puntatore per la classe Base pBase su un oggetto della Derivata, si devono e si possono “tagliare” le informazioni all'oggetto della Derivata che non esistono nella classe Base.
  2. Per utilizzare il puntatore per la classe Derivata pDeriv su un oggetto della Base, si dovrebbero “aggiungere” delle informazioni all'oggetto della classe Base che esisterebbero in un oggetto della classe Derivata, ma cosa si potrebbe aggiungere???? Non ci si può inventare le informazioni che non esistono

Poiché, nella relazione di ereditarietà, un puntatore della classe Base può puntare anche un oggetto della classe Derivata, SE nel programma uso i puntatori agli oggetti, ALLORA potrebbe generarsi un'ambiguità durante le chiamate delle funzioni. Come in questo esempio:

void Base::fun()
{
   //...
}

void Derivata::fun()
{
   //...
}

int main()
{
   Base* pBase = new Base();
   Derivata* pDeriv = new Derivata();
   pBase = pDerivata;   // scambio dell'oggetto puntato
   pBase->fun();        // quale funzione chiama? Base::fun() oppure Derivata::fun()
                        // il puntatore è di tipo Base*, l'oggetto puntato è di tipo Derivata

}

Questo tipo di ambiguità, in C++, viene risolta al momento della compilazione, invocando la funzione della classe del puntatore Basefun(). Questa soluzione è più efficiente e si chiama early binding (o anche static binding). ===== Late binding ===== Viceversa, se dichiaro la funzione virtual, allora viene chiamata quella della Derivata (late binding). <code> virtual void Basefun() {

 //...

}

virtual void Derivatafun() { … } int main() { Base* pBase = new Base(); Derivata* pDeriv = new Derivata(); pBase = pDerivata; scambio dell'oggetto puntato pBase→fun(); quale funzione chiama? Basefun() oppure Derivatafun() la funzione BASEfun() è virtual quindi

                      // ora non conta il tipo di puntatore ma l'oggetto puntato

} </code>

È Il tipo di oggetto che invoca la funzione virtual a determinare quale versione di tale funzione deve essere usata. Quest'ultimo sistema, insieme alle classi astratte, consente di creare delle generiche classi Base, con delle funzioni membro virtuali che verranno implementate dalle classi da loro derivate. Tutto questo SENZA modificare il vecchio codice della classe Base. Il late binding è più flessibile ma meno efficiente rispetto al early binding.

Quando progetto una classe, se voglio che nelle chiamate di funzione comandi il tipo di puntatore, invece del dell'oggetto puntato, non uso virtual.

L'ambiguità ovviamente non esiste se non uso i puntatori. (esempio: unOggetto.unMetodo();)

  • Le funzioni “definitevirtual nella classe Base possono essere “ridefinite” nella classe Derivata. In caso contrario esse rimangono virtual anche nella classe Derivata, senza obbligo di ripetere la parola virtual
  • I costruttori NON possono essere virtual.
  • La funzione membro virtual della classe Derivata può richiamare quella della Base usando l'operatore
    ::
  • Le funzioni membro virtual, quando sono chiamate dentro un costruttore/distruttore, sono quelle della Base

Classe astratta

Vedere anche relazione di realizzazione

Questo tipo di classe si usa quando la classe non deve rappresentare concetti concreti e/o quando non si devono realizzare mai oggetti di questo tipo. Grazie all'uso delle classi astratte insieme al polimorfismo si possono realizzare delle librerie facilmente espandibili con nuove classi

  • Nella classe astratta deve esserci almeno una funzione membro virtuale pura.
    void fun(intx) = 0;
  • Nella classe astratta non viene definita nessuna delle sue funzioni membro.
  • Una classe Derivata da una classe Base astratta, è a sua volta classe astratta, se la funzione virtuale pura non viene implementata nella Derivata.
  • appunti3s/polimorfismo.txt
  • Last modified: 2018/04/25 07:55
  • (external edit)