Overloading Differenze nei vari linguaggi di Elisa Trifirò e Barbara Tacchino Argomenti trattati Overloading: un tipo di Polimorfismo Overloading in Java Overloading in C++ Overloading in C# Conclusioni Classificazione di Cardelli-Wegner La classificazione più completa risale al 1985 ed è opera di Luca Cardelli e Peter Wegner: Polimorfismo universale Sono quelle funzioni che possono operare su più tipi purché vengano rispettate alcune proprietà. Polimorfismo parametrico: programmazione generica. Polimorfismo per inclusione: programmazione ad oggetti. Polimorfismo parametrico Idea: utilizzare un tipo come parametro in una funzione generica. E’ il compilatore che genera di volta in volta l’implementazione corretta. Esempio: template in C++ Polimorfismo per inclusione Nell'approccio object-oriented si definisce la classe degli elementi su cui ha senso applicare una certa funzione. Tutte le classi da essa derivate ne erediteranno le funzioni, con la possibilità di ridefinirle. Polimorfismo ad-hoc Nel polimorfismo ad-hoc rientrano: Overloading Coercion Coercion Coercion: quando un valore di un tipo viene visto (e talvolta trasformato) come valore di un altro tipo. Esempio: nel sommare un float e un double, avviene una promozione da float a double. Overloading Il termine overloading (da to overload) significa sovraccaricamento e overloading delle funzioni indica la possibilità di attribuire allo stesso nome di funzione più significati. Esempio: int x = 7 * 5; double y = 3.1 * 7.4; Overloading in Java In Java é ammesso l’overloading nel senso che é possibile definire più versioni di un metodo o costruttore che differiscono per la lista degli argomenti. Esempio class MyComplex{ double a; double b; double m (MyComplex p){return p.a;} double m(double h){return this.a+h;} } Nota: non possono differire per il tipo di ritorno. Esempio In Java non é ammesso l’overloading di operatori. class MyComplex{ double a,b; public MyComplex add(MyComplex m){ this.a+=m.a; this.b+=m.b return new MyComplex(this.a,this.b);}} Risoluzione dell’overloading in Java La risoluzione dell’overloading é la scelta della versione da applicare ad ogni chiamata di metodo. In Java é fatta staticamente ossia al momento della compilazione. Algoritmo di risoluzione dell’overloading in Java 1. 2. 3. L’algoritmo di risoluzione é il seguente: Si cercano tutte le versioni del metodo che potrebbero essere applicate, cioè con il nome giusto e con i tipi (statici) Se si trovano più versioni applicabili, si elimina via via ogni versione meno “specifica” di un’altra Se alla fine si arriva ad una sola versione, è quella da applicare, altrimenti si ha un errore statico Considerazioni sulla risoluzione dell’overloading in Java La risoluzione dell’overloading in Java corrisponde ad un preprocessing che sostituisce al nome del metodo un nome esteso con le opportune informazioni di tipo. Un esempio errato public class B { public void unMetodo() { System.out.println(“Glup!”); } public int unMetodo() { System.out.println(“Glup!”); return 1; } } Questa classe dà errore in compilazione perché non vi possono essere due metodi con lo stesso nome e stessi parametri anche se con tipi di ritorno differenti. Dynamic binding in Java A run-time viene considerato il tipo dinamico del ricevitore della chiamata di metodo e da quello avviene la ricerca gerarchica del metodo da scegliere. Non viene considerato il tipo dinamico dei parametri. Si continua ad andare in alto nella gerarchia di classi finché non si arriva al primo applicabile esattamente al tipo dinamico. Esempio class A { h(int i){return 0}; f(){return 1}; s(double d){return 2}; } class B extends A { f(){return 3}; h(int i){return 4}; s(int i){return 5};} class C extends A { f(){return 6}; } A a1=new B(); A a2=new C(); B b=new B(); A a=new A(); Esempio a.s(3.0); //2 a1.s(3.0); //2 b.s(0); //5 b.s(1.0); //2 ((A) a1).h(4); //4 Overloading in C++ 1. 2. In C++ è permesso: Due funzioni con lo stesso nome ma parametri e tipi di ritorno differenti: int media(int * array, int size); double media(double a,double b); Due parametri passati per riferimento che differiscono per il qualificatore const sono considerati differenti: void f(int&); void f(const int&); Esempio In C++ e’ permesso l’overloading di operatori: class MyComplex{ double a,b; MyComplex operator +(const MyComplex &obj) { MyComplex temp; temp.a=(*this).a+obj.a; temp.b=(*this).b+obj.b; return temp; } }; Implementazione dell’overloading in C++ Ad ogni funzione del programma il compilatore assegna un nome interno che dipende dal nome della funzione e dai suoi parametri. Il meccanismo di risoluzione dipende dal compilatore. Risoluzione dell’overloading in C++ Si passa attraverso un processo di corrispondenza tra gli argomenti della chiamata e i parametri della funzione. Il compilatore confronta gli argomenti della chiamata con le varie definizioni e se possibile ne invoca una. Algoritmo di risoluzione dell’overloading in C++ 1. 2. La risoluzione avviene in tre passi: Identificazione delle funzioni candidate (funzioni che hanno lo stesso nome della funzione chiamata) Scelta delle funzioni utilizzabili (funzioni i cui parametri corrispondono agli argomenti della chiamata per numero e tipo) Algoritmo di risoluzione dell’overloading in C++ 2 3. Scelta della migliore funzione utilizzabile ( funzione per cui gli argomenti corrispondono come tipi statici ai parametri) Overloading in C# Si possono dichiarare più metodi con lo stesso nome ma parametri diversi. E’ permesso l’overloading di operatori. E’ permesso l’overloading con tipi di ritorno diverso. Esempio class MyComplex { double a,b; public static operator + (MyComplex a,MyComplex q) { MyComplex z=new MyComplex((p.a+q.a),(p.b+q.b)); return z; }} Overloading di operatori in C# Gli operatori unari che possono essere overloaded in C# sono : + - ! ~ ++ -- true false Gli operatori binari che possono essere overloaded sono : + - * / % & | ^ << >> == != > < >= <= Risoluzione dell’overloading in C# L’insieme delle funzioni candidate è ridotto a quelle funzioni applicabili rispetto alla lista degli argomenti. Se questo insieme è vuoto si ha un errore in compilazione. Altrimenti è scelta la migliore possibile. Risoluzione dell’Overloading in C# L’algoritmo di risoluzione è simile a quello utilizzato dal linguaggio Java. Esempio class A{…} class B : A {…} class C : B {…} class D { public static void public static void public static void public static void m(A m(B m(A m(B a){…} b){…} a, B b){…} b, A a){…} } Esempio (continuo) A a=new A(); C c=new C(); D d=new D(); d.m(a);/* D.m(A a) => d.m(A);*/ d.m(c);/* D.m(A a), D.m(B b)=>d.m(B) D.m(c,c);/*D.m(A a,B b), D.m(B b, A a) Error Risoluzione degli operatori in C# Le implementazioni degli operatori definite dall’utente hanno la precedenza su quelli predefiniti. Overloading a run-time A differenza di Java durante l’esecuzione, viene ignorato il tipo dinamico dell’oggetto che ha invocato il metodo e viene quindi applicato quello scelto a compile-time. Linguaggio overloading consentito C++ costruttori, distruttori, funzioni, operatori (tutti compresi ',' , 'new', 'delete' e '->') conversioni implicite (cast) Java costruttori e funzioni C# costruttori, funzioni e operatori ad esclusione di: '.', =, &&, ||, ?, : new e tutti gli operatori di assegnazione composti come += *= perché vengono sempre eseguiti con l'espansione delle operazioni semplici (evitando i problemi in cui sia stato fatto l'overloading degli operatori semplici e non di quelli composti) conversioni implicite (cast) Conclusioni L’overloading, se usato correttamente, migliora la leggibilità del codice evitando nomi con ridondanza di informazioni. Esempio Volendo definire una funzione che calcoli la potenza di interi e un’atra per i float, potremmo chiamarle in due modi diversi (es.IPow e FPow). Questo però nasconde la similarità della loro funzionalità. Conclusioni E’ difficile da gestire per il compilatore. Il dynamic binding causa un rallentamento nell’esecuzione. Il dynamic binding a run-time potrebbe dare errori non segnalati in compile-time.