Polimorfismo in C++
Il polimorfismo è uno dei pilastri della programmazione orientata agli oggetti (OOP) e rappresenta la capacità di utilizzare una singola interfaccia per oggetti di diversi tipi. In C++, il polimorfismo consente di trattare oggetti di diverse classi derivate attraverso un puntatore o riferimento di una classe base, permettendo una maggiore flessibilità ed estensibilità del codice. Questo articolo esplorerà il concetto di polimorfismo in C++, i tipi principali di polimorfismo e come implementarlo efficacemente nel tuo codice.
Tipi di Polimorfismo
Esistono principalmente due tipi di polimorfismo in C++:
- Polimorfismo di Sovraccarico (Ad-hoc Polymorphism): Si riferisce alla capacità di un singolo nome di funzione o operatore di comportarsi in modi diversi in base ai tipi degli argomenti. Comprende l’overload delle funzioni e l’overload degli operatori.
- Polimorfismo di Sottotipo (Subtyping Polymorphism) o Polimorfismo di Inclusione: Si verifica quando una funzione tratta oggetti di diverse classi derivate come oggetti di una classe base comune. È implementato principalmente attraverso l’uso di funzioni virtuali.
Esempio di Polimorfismo di Sovraccarico
Il polimorfismo di sovraccarico è già stato trattato in articoli precedenti attraverso l’overload delle funzioni e degli operatori.
Esempio di Polimorfismo di Sottotipo
Il polimorfismo di sottotipo consente a un puntatore o riferimento di una classe base di invocare metodi di una classe derivata.
Implementazione del Polimorfismo tramite Funzioni Virtuali
Per implementare il polimorfismo di sottotipo in C++, utilizziamo funzioni virtuali. Una funzione virtuale è una funzione membro di una classe base che può essere sovrascritta in una classe derivata. Quando una funzione virtuale viene chiamata su un puntatore o riferimento a una classe base, C++ invoca la versione della funzione definita nella classe derivata più specifica.
Esempio di Classe Base e Derivate
Supponiamo di avere una classe base Forma
e due classi derivate Cerchio
e Rettangolo
.
#include <iostream>
#include <cmath>
class Forma {
public:
virtual void disegna() const {
std::cout << "Disegna una forma generica." << std::endl;
}
virtual double area() const = 0; // Funzione virtuale pura
};
class Cerchio : public Forma {
private:
double raggio;
public:
Cerchio(double r) : raggio(r) {}
void disegna() const override {
std::cout << "Disegna un cerchio." << std::endl;
}
double area() const override {
return M_PI * raggio * raggio;
}
};
class Rettangolo : public Forma {
private:
double larghezza, altezza;
public:
Rettangolo(double l, double h) : larghezza(l), altezza(h) {}
void disegna() const override {
std::cout << "Disegna un rettangolo." << std::endl;
}
double area() const override {
return larghezza * altezza;
}
};
Uso del Polimorfismo
Il polimorfismo permette di trattare oggetti di classi diverse, ma con un’interfaccia comune, in modo uniforme.
int main() {
Forma* forme[2];
forme[0] = new Cerchio(5.0);
forme[1] = new Rettangolo(4.0, 6.0);
for (int i = 0; i < 2; ++i) {
forme[i]->disegna();
std::cout << "Area: " << forme[i]->area() << std::endl;
}
// Deallocazione della memoria
delete forme[0];
delete forme[1];
return 0;
}
Uscita
Disegna un cerchio.
Area: 78.5398
Disegna un rettangolo.
Area: 24
In questo esempio, anche se il tipo di forme[i]
è Forma*
, il metodo corretto (disegna
e area
) per ciascuna classe derivata viene chiamato grazie al polimorfismo.
Funzioni Virtuali Pure e Classi Astratte
In C++, una funzione virtuale pura è una funzione che deve essere sovrascritta in ogni classe derivata. Viene dichiarata nella classe base con = 0
, rendendo la classe base una classe astratta. Una classe astratta non può essere istanziata direttamente.
Esempio di Classe Astratta
Nel nostro esempio, la classe Forma
è una classe astratta poiché la funzione area
è una funzione virtuale pura.
class Forma {
public:
virtual void disegna() const = 0; // Funzione virtuale pura
virtual double area() const = 0; // Funzione virtuale pura
};
Vantaggi del Polimorfismo
- Flessibilità: Il polimorfismo permette di scrivere codice flessibile e riutilizzabile, in grado di lavorare con diversi tipi di dati attraverso un’interfaccia comune.
- Estensibilità: È facile estendere il sistema aggiungendo nuove classi derivate senza modificare il codice esistente.
- Manutenibilità: Riduce la duplicazione del codice, migliorando la manutenibilità.
Limitazioni del Polimorfismo
- Overhead di Prestazioni: L’uso del polimorfismo comporta un overhead di runtime, poiché le chiamate a funzioni virtuali richiedono il lookup nella vtable (tabella virtuale).
- Complessità: L’implementazione e la gestione del polimorfismo possono aumentare la complessità del sistema, specialmente in gerarchie di classi complesse.
Best Practices
- Usare il polimorfismo quando è necessario: Non abusare delle funzioni virtuali; utilizzale solo quando è necessario un comportamento diverso nelle classi derivate.
- Gestione della memoria: Quando usi il polimorfismo con puntatori, assicurati di gestire correttamente la memoria, preferibilmente usando smart pointers come
std::unique_ptr
ostd::shared_ptr
. - Documentare l’uso delle funzioni virtuali: Le funzioni virtuali dovrebbero essere ben documentate, soprattutto quando sono virtuali pure, per chiarire il contratto che le classi derivate devono rispettare.
Conclusione
Il polimorfismo è uno strumento potente in C++ che permette di scrivere codice flessibile, estensibile e più facile da mantenere. Comprendere come implementare e utilizzare correttamente il polimorfismo tramite funzioni virtuali e classi astratte è fondamentale per qualsiasi sviluppatore C++ che desideri sfruttare al massimo la programmazione orientata agli oggetti. Con un uso accorto e ben pianificato del polimorfismo, è possibile creare sistemi complessi che rimangono flessibili e facili da estendere nel tempo.