L’overloading è uno degli aspetti fondamentali del C++. Infatti l’overloading non solo fornisce il sopporto per il polimorfismo in fase di compilazione ma aggiunge al linguaggio flessibilità e comodità.
Come ben sappiamo gli operatori del C ammettono solo gli operandi che appartengono ai tipi nativi del linguaggio; in C, ad esempio, non é ammesso "sommare" due oggetti, se questi sono istanze di una struttura. Grazie all’overloading è possibile forviare questo problema (estendibilità del C++).
Il C++ consente di effettuare l’overloading sia di funzioni, che degli operatori (salvo ::, ?:, sizeof, e pochi altri); consiste nell’impiegare lo stesso nome per due funzioni o dare un significato multiplo agli operatori. Il segreto dell’overloading sta nel fatto che ogni ridefinizione di una certa funzione (risp. operatore) deve utilizzare parametri di tipo differente, oppure in numero differente. Grazie a queste differenze, il compilatore sa quale funzione (risp. operatore) richiamare in una determinata situazione. In questo modo si riesce ad attribuire un nuovo significato agli operatori e alle funzioni.
Detto ciò passiamo ora a vedere come è possibile effettuare l’overloading delle funzioni e degli separatori. Per semplicità, nella maggior parte dei casi la possibile implementazione dei vari overloading è rimandata in futuro: vedi matrice, lista e albero.
Overloading delle funzioni.
Per ottenere l’overloading di una funzione bisogna creare una funzione
che il suo stesso nome, ma con differenti parametri. Chiariamo il concetto con
un esempio:
#include <iostream>
using namespace std
int myfunc(int i);
double myfunc(double i); // è differente nel tipo dei parametri
int myfunc(int i, int j); // è differente nel numero dei parametri
int main()
{
cout<<myfunc(10)<<endl; // richiama myfunc(int i)
cout<<myfunc(6.5)<<endl; // richiama myfunc(int i)
cout<<myfunc(10, 20)<<endl; // richiama myfunc(int i, int j)
}
int myfunc(int i)
{
return i;
}
double myfunc(double i)
{
return i;
}
int myfunc(int i, int j)
{
return i+j;
}
Come si è detto la ridefinizione di una certa funzione deve utilizzare parametri di tipo differente e/o in numero differente, dunque due funzioni non possono differire solo per il tipo di dati restituiti. Ad esempio, ecco un modo errato di eseguire l’overloading della funzione myfunc():
int myfunc(int i);
char myfunc(int i);
Tra le funzioni modificate tramite overloading, quelle più importanti sono i costruttori. La forma più importante è costituita dal costruttore di copie. La creazione di un costruttore di copie può aiutare a evitare i problemi che sorgono quando si utilizza un oggetto per inizializzarne un altro. In tal caso si evita di utilizzare il costruttore bit a bit (i due oggetti saranno due copie esatte, ma non utilizzeranno la stessa area di memoria). Vi sono tre motivi che spingono ad eseguire l’overloading di una funzione costruttore: la maggiore flessibilità, la possibilità di creare oggetti inizializzati e non inizializzati e la possibilità di definire costruttori di copie. Chiariamo il concetto con un esempio:
class myclass{
int x;
public:
// overloading del costruttori in più modi: c’è maggiore flessibilità
myclass(int a);
myclass(int a, int b);
myclass() {x = 0}; // overloading senza inizializzatore
myclass(int n) {x = n}; // overloading con inizializzatore
myclass(const myclass &C); // costruttore di copie
}
Overloading degli operatori.
Per ottenere l'overloading di un operatore bisogna creare una funzione con nome
operator seguito dal simbolo dell'operatore (es.: operator+).
Se l'operatore si applicherà a una classe, la funzione può essere
definita come metodo (pubblico) della classe, oppure può essere esterna,
nel qual caso va inserita come friend nella definizione della classe. La scelta
è abbastanza libera: in genere conviene che la funzione sia un metodo
quando può modificare i suoi operandi; in altri casi è obbligatorio
che sia friend. Gli argomenti della funzione devono corrispondere agli operandi
dell'operatore. Ne consegue che per gli operatori unari è necessario
un solo argomento, per quelli binari ce ne vogliono due (e nello stesso ordine,
cioè il primo argomento deve corrispondere al left-operand e il secondo
argomento al right-operand). Si consiglia, anche se non è obbligatorio,
che gli operatori mantengano comunque qualche "somiglianza" con il
loro significato originario.
Tuttavia bisogna ricordare che, se la funzione è metodo di una classe, il C++ aggiunge come primo argomento, nella traduzione interna della chiamata, il puntatore nascosto this all'oggetto in cui la funzione é incapsulata; ne consegue che la funzione può essere un metodo solo se il primo operando è l'oggetto stesso (e in tal caso il numero di argomenti della funzione deve essere ridotto di un'unità); in caso contrario la funzione deve essere friend esterna. Chiariamo il concetto con un esempio:
definita la classe A con una sua istanza a, e definita una variabile k di tipo int, vogliamo creare due overloading dell'operatore "+" in modo che abbiano significato le operazioni a + k e k + a (possibilmente con lo stesso risultato, per mantenere valida la proprietà commutativa dell'operazione). In entrambi i casi le funzioni devono restituire un oggetto della classe A, ma, mentre nel primo caso il primo operando è anch'esso oggetto della classe A e quindi la funzione può essere metodo della classe stessa, nel secondo caso no e pertanto la funzione deve essere esterna:
// operazione a + k
A A::operator+(int k)
// la funzione è un metodo di A (il primo operando è nascosto)// operazione k + a
A operator+(int k, A a)
// la funzione è esterna e va dichiarata come friend nella definizione della classe
A: friend A operator+(int, A);
Nel caso che gli argomenti della funzione non debbano essere modificati, è buona norma prudenziale usare la parola-chiave const (seguitando nell'esempio):
A operator+(const int k, const A a)
Inoltre, il C++ lavora più rapidamente se gli argomenti sono passati byreference:
A operator+(const int& k, const A& a)
Powered by Vincenzo Capuano