next up previous contents index
Next: Classi astratte Up: Cenni di polimorfismo Previous: Il problema   Indice   Indice analitico

La soluzione

La programmazione orientata agli oggetti fino ad ora sperimentata, per i motivi illustrati or ora, non è completa: sarà il polimorfismo a chiudere il cerchio, consentendo tecniche che sono del tutto nuove a molti altri linguaggi di programmazione. Ad esempio, fino a quando abbiamo trattato di classi e composizione, una buona programmazione in C avrebbe potuto eguagliare i nostri risultati, seppure con uno sforzo superiore. Con la ereditarietà siamo già entrati in un mondo ``diverso'', l'astrazione ha raggiunto livelli molto elevati, e solo con estrema difficoltà la programmazione in C di un codice scritto in C++ darebbe risultati altrettanto soddisfacenti. Adesso che stiamo per presentare il polimorfismo, cè da dire che il C è finito, non può fare più nulla di neanche lontanamente paragonabile. Il tutto con una semplicissima keyword: virtual.

Consideriamo il problema dell'early binding; in C++ possiamo forzare il compilatore ad effettuare il collegamento della funzione con il corpo del programma non a tempo di compilazione, ma a tempo di esecuzione; tale tipo di collegamento è detto late binding, (``early'' = presto, ``late'' = tardi), oppure dynamic binding o runtime binding. Non ci addentreremo affatto nello scoprire come sia possibile che il compilatore metta in atto tale tecnica, perché risulterebbe al di là degli scopi di questo libro. Comunque, può sembrare incredibile, ma funziona. All'interno di una classe la parola chiave virtual premessa alla dichiarazione di una funzione, indica che su di essa andrà messo in atto il late binding. Modifichiamo leggermente ex12_5_1.cpp, inserendo virtual prima della dichiarazione di suona, e possiamo osservare come, questa volta, il programma dia l'esito che avremmo sin dal primo momento desiderato.


// ex12_5_3.cpp

/* come ex12_5_1.cpp */

class Flauto {
public:
  virtual void suona (Nota) {  // UNICA riga modificata
    cout << "Flauto::suona()\n"; }
};

/* come ex12_5_1.cpp */

void main() {
  Flauto f;
  FDolce fd;
  suonaNota (5, f);   // Flauto::suona()  // ok
  suonaNota (3, fd);  // FDolce::suona()  // OK
}

Riguardo la sintassi di virtual, si tenga in considerazione che solo la dichiarazione va dichiarata virtual, non la definizione; inoltre se una funzione è virtual in una classe, lo è anche in tutte le classi da esse derivate. La sovrapposizione di funzioni virtuali è detta, piuttosto che overloading, overriding; per motivi tecnici non è possibile dichiarare virtuali le funzioni inline. Naturalmente in una classe possono coesistere funzioni virtuali e non, come vediamo nel seguete esempio, basato su ex12_5_2.cpp:


// ex12_5_2.cpp

#include <iostream.h>
#include <math.h>
const double PIGRECO       = 3.1415926536;
const double PIGRECO_MEZZI = 1.5707963268;

class Para {
  double a, b, theta;
public:
  Para (double A, double B, double Theta)
    : a(A), b(B), theta(Theta) { }
  virtual void stampaNome () {
    cout << "Parallelogramma\n"; }
  double perimetro () {
    return (a + b) * 2; }
  virtual double area () {
    return a * b * sin(theta); }
};

class Rett : public Para {
  double base, altezza;
public:
  Rett (double Base, double Altezza)
    : Para (Base, Altezza, PIGRECO_MEZZI),
      base(Base), altezza(Altezza) { }
  void stampaNome () {
    cout << "Rettangolo\n"; }
  double area () {
    return base * altezza; }
};

class Quad : public Rett {
public:
  Quad (double lato)
    : Rett (lato, lato) {}
  void stampaNome () {
    cout << "Quadrato\n"; }
};

void main() {
  Para** array = new Para*[3];
  array[0] = new Para (10.0, 5.0, PIGRECO / 6.0);
  array[1] = new Rett (9.0, 4.0);
  array[2] = new Quad (8.0);

  for (int i = 0; i < 3; i++) {
    array[i]->stampaNome();
    cout << "\tperimetro: " << array[i]->perimetro() << "\n";
    cout << "\tarea:      " << array[i]->area() << "\n";
  }
}

output:


Parallelogramma
        perimetro: 30
        area:      25
Rettangolo
        perimetro: 26
        area:      36
Quadrato
        perimetro: 32
        area:      64

Ciascuna classe contiene tre funzioni:

Grazie al polimorfismo è possibile dunque effettuare degli upcast senza perdere le informazioni peculiari delle classi più specializzate. Ci sono linguaggi di programmazione (Smalltalk, Java) nei quali tutte le funzioni sono virtuali; nel C++ si è scelto di dare al programmatore la possibilità di scegliere quali debbano essere le funzioni virtuali e quali non. Il problema (molto relativo) con il polimorfismo è che si ha una leggere perdita di efficienza nella chiamata delle funzioni a tempo di esecuzione, sufficiente a scoraggiare, nel momento in cui il C++ nacque, i programmatori C nel passaggio a esso.


next up previous contents index
Next: Classi astratte Up: Cenni di polimorfismo Previous: Il problema   Indice   Indice analitico
Claudio Cicconetti
2000-09-06