Salta ai contenuti
Manuale LogoManuale Logo

Polimorfismo

Supponi di dover invocare lo stesso metodo su cani, gatti e mucche. Senza polimorfismo dovresti distinguere ogni tipo.

Con il polimorfismo, puoi conservare gli oggetti in una stessa lista e lasciare che ciascuno esegua la propria versione di emettiSuono.

Polimorfismo significa “molte forme”: lo stesso metodo può avere comportamenti diversi in base all’oggetto.

class Cane {
public:
void suona() { cout << "Bau!" << endl; }
};
class Gatto {
public:
void suona() { cout << "Miao!" << endl; }
};
// Senza polimorfismo, serve una funzione per ogni tipo
void faiSuonare(Cane& c) { c.suona(); }
void faiSuonare(Gatto& g) { g.suona(); }
// E se aggiungi Mucca? Devi aggiungere un'altra funzione...

Aggiungendo virtual davanti a un metodo nella classe madre, dici al C++ di usare il comportamento della classe figlia, non della madre:

class Animale {
public:
string nome;
virtual void emettiSuono() { // virtual = usa il metodo della figlia
cout << nome << " emette un suono." << endl;
}
};
class Cane : public Animale {
public:
void emettiSuono() override { // override = sovrascrive il metodo base
cout << nome << " dice: Bau!" << endl;
}
};
class Gatto : public Animale {
public:
void emettiSuono() override {
cout << nome << " dice: Miao!" << endl;
}
};

La parola override non è obbligatoria, ma è una buona abitudine: il compilatore ti avvisa se stai cercando di sovrascrivere un metodo che non esiste nella classe madre.

Il comportamento si vede quando usi un puntatore alla classe madre per gestire oggetti di classi figlie diverse:

Animale* a1 = new Cane(); // puntatore ad Animale, ma è un Cane
Animale* a2 = new Gatto(); // puntatore ad Animale, ma è un Gatto
a1->nome = "Fido";
a2->nome = "Luna";
a1->emettiSuono(); // Fido dice: Bau! (usa il metodo di Cane!)
a2->emettiSuono(); // Luna dice: Miao! (usa il metodo di Gatto!)
delete a1;
delete a2;

Senza virtual, entrambi avrebbero chiamato il metodo di Animale (suono generico). Con virtual, il C++ sa di dover usare il metodo del tipo reale dell’oggetto.

Questo è il punto di forza del polimorfismo: puoi mettere tipi diversi in una stessa lista e trattarli tutti allo stesso modo:

#include <iostream>
#include <vector>
using namespace std;
class Animale {
public:
string nome;
Animale(string n) : nome(n) {}
virtual void emettiSuono() = 0; // obbliga le classi figlie a implementarlo
virtual ~Animale() {} // distruttore virtuale (importante!)
};
class Cane : public Animale {
public:
Cane(string n) : Animale(n) {}
void emettiSuono() override { cout << nome << ": Bau!" << endl; }
};
class Gatto : public Animale {
public:
Gatto(string n) : Animale(n) {}
void emettiSuono() override { cout << nome << ": Miao!" << endl; }
};
class Mucca : public Animale {
public:
Mucca(string n) : Animale(n) {}
void emettiSuono() override { cout << nome << ": Mu!" << endl; }
};
int main() {
vector<Animale*> fattoria;
fattoria.push_back(new Cane("Fido"));
fattoria.push_back(new Gatto("Luna"));
fattoria.push_back(new Mucca("Bessie"));
fattoria.push_back(new Cane("Rex"));
// Un solo ciclo gestisce tutti i tipi di animali!
for (Animale* a : fattoria) {
a->emettiSuono();
}
// Libera la memoria
for (Animale* a : fattoria) {
delete a;
}
return 0;
}

Output:

Fido: Bau!
Luna: Miao!
Bessie: Mu!
Rex: Bau!

Quando scrivi = 0 dopo un metodo virtuale, quel metodo diventa puro virtuale: non ha corpo nella classe madre e ogni classe figlia è obbligata a implementarlo.

Una classe con almeno un metodo puro virtuale è astratta: non puoi creare oggetti direttamente da essa.

class Forma {
public:
// Ogni forma DEVE implementare questi due metodi
virtual double area() const = 0;
virtual double perimetro() const = 0;
// Questo metodo usa i precedenti (funziona grazie al polimorfismo)
void stampa() const {
cout << "Area: " << area() << ", Perimetro: " << perimetro() << endl;
}
};
class Cerchio : public Forma {
private:
double raggio;
public:
Cerchio(double r) : raggio(r) {}
double area() const override { return 3.14159 * raggio * raggio; }
double perimetro() const override { return 2 * 3.14159 * raggio; }
};
class Rettangolo : public Forma {
private:
double base, altezza;
public:
Rettangolo(double b, double a) : base(b), altezza(a) {}
double area() const override { return base * altezza; }
double perimetro() const override { return 2 * (base + altezza); }
};
int main() {
// Forma f; // ERRORE: Forma è astratta, non puoi creare oggetti diretti
Cerchio c(5.0);
Rettangolo r(4.0, 6.0);
c.stampa(); // Area: 78.5397, Perimetro: 31.4159
r.stampa(); // Area: 24, Perimetro: 20
return 0;
}

Quando usi il polimorfismo con puntatori alla classe madre, dichiara sempre il distruttore della classe madre come virtual. Altrimenti, quando elimini un oggetto con delete, viene chiamato solo il distruttore della madre, non quello della figlia:

class Base {
public:
virtual ~Base() { // distruttore virtuale
cout << "Base distrutta" << endl;
}
};
class Derivata : public Base {
public:
~Derivata() override {
cout << "Derivata distrutta" << endl;
}
};
Base* ptr = new Derivata();
delete ptr; // con virtual: chiama Derivata prima, poi Base
// senza virtual: chiama solo Base (bug!)