Ci sono molti post che si lamentano del sovraccarico dell'operatore.
Sentivo di dover chiarire i concetti di "sovraccarico dell'operatore", offrendo un punto di vista alternativo su questo concetto.
Codice offuscante?
Questo argomento è un errore.
Offuscare è possibile in tutte le lingue ...
È facile offuscare il codice in C o Java attraverso funzioni / metodi come lo è in C ++ tramite sovraccarichi dell'operatore:
// C++
T operator + (const T & a, const T & b) // add ?
{
T c ;
c.value = a.value - b.value ; // subtract !!!
return c ;
}
// Java
static T add (T a, T b) // add ?
{
T c = new T() ;
c.value = a.value - b.value ; // subtract !!!
return c ;
}
/* C */
T add (T a, T b) /* add ? */
{
T c ;
c.value = a.value - b.value ; /* subtract !!! */
return c ;
}
... Anche nelle interfacce standard di Java
Per un altro esempio, vediamo l' Cloneable
interfaccia in Java:
Dovresti clonare l'oggetto che implementa questa interfaccia. Ma potresti mentire. E crea un oggetto diverso. In effetti, questa interfaccia è così debole che potresti restituire un altro tipo di oggetto, solo per divertimento:
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
Dato che l' Cloneable
interfaccia può essere abusata / offuscata, dovrebbe essere vietata per gli stessi motivi che si suppone dovrebbe essere il sovraccarico dell'operatore C ++?
Potremmo sovraccaricare il toString()
metodo di una MyComplexNumber
classe per far sì che restituisca l'ora del giorno rigorosa. Anche il toString()
sovraccarico dovrebbe essere vietato? Potremmo sabotare MyComplexNumber.equals
per avere un valore casuale, modificare gli operandi ... ecc. Ecc. Ecc.
In Java, come in C ++ o in qualsiasi linguaggio, il programmatore deve rispettare un minimo di semantica durante la scrittura del codice. Ciò significa implementare una add
funzione che aggiunge e un Cloneable
metodo di implementazione che clona e un ++
operatore che incrementa.
Cosa c'è di offuscante comunque?
Ora che sappiamo che il codice può essere sabotato anche attraverso i metodi Java incontaminati, possiamo chiederci quale sia il reale utilizzo del sovraccarico dell'operatore in C ++?
Notazione chiara e naturale: metodi contro sovraccarico dell'operatore?
Confronteremo di seguito, per casi diversi, lo "stesso" codice in Java e C ++, per avere un'idea di quale tipo di stile di codifica sia più chiaro.
Confronti naturali:
// C++ comparison for built-ins and user-defined types
bool isEqual = A == B ;
bool isNotEqual = A != B ;
bool isLesser = A < B ;
bool isLesserOrEqual = A <= B ;
// Java comparison for user-defined types
boolean isEqual = A.equals(B) ;
boolean isNotEqual = ! A.equals(B) ;
boolean isLesser = A.comparesTo(B) < 0 ;
boolean isLesserOrEqual = A.comparesTo(B) <= 0 ;
Si noti che A e B potrebbero essere di qualsiasi tipo in C ++, purché siano previsti sovraccarichi dell'operatore. In Java, quando A e B non sono primitivi, il codice può diventare molto confuso, anche per oggetti di tipo primitivo (BigInteger, ecc.) ...
Accessori e sottoscrizioni di array / container naturali:
// C++ container accessors, more natural
value = myArray[25] ; // subscript operator
value = myVector[25] ; // subscript operator
value = myString[25] ; // subscript operator
value = myMap["25"] ; // subscript operator
myArray[25] = value ; // subscript operator
myVector[25] = value ; // subscript operator
myString[25] = value ; // subscript operator
myMap["25"] = value ; // subscript operator
// Java container accessors, each one has its special notation
value = myArray[25] ; // subscript operator
value = myVector.get(25) ; // method get
value = myString.charAt(25) ; // method charAt
value = myMap.get("25") ; // method get
myArray[25] = value ; // subscript operator
myVector.set(25, value) ; // method set
myMap.put("25", value) ; // method put
In Java, vediamo che per ogni contenitore fare la stessa cosa (accedere al suo contenuto attraverso un indice o identificatore), abbiamo un modo diverso di farlo, il che è confuso.
In C ++, ogni container utilizza lo stesso modo per accedere al suo contenuto, grazie al sovraccarico dell'operatore.
Manipolazione di tipi avanzati naturali
Gli esempi seguenti usano un Matrix
oggetto, trovato usando i primi collegamenti trovati su Google per " oggetto Matrix Java " e " oggetto Matrix C ++ ":
// C++ YMatrix matrix implementation on CodeProject
// http://www.codeproject.com/KB/architecture/ymatrix.aspx
// A, B, C, D, E, F are Matrix objects;
E = A * (B / 2) ;
E += (A - B) * (C + D) ;
F = E ; // deep copy of the matrix
// Java JAMA matrix implementation (seriously...)
// http://math.nist.gov/javanumerics/jama/doc/
// A, B, C, D, E, F are Matrix objects;
E = A.times(B.times(0.5)) ;
E.plusEquals(A.minus(B).times(C.plus(D))) ;
F = E.copy() ; // deep copy of the matrix
E questo non si limita alle matrici. Le classi BigInteger
e BigDecimal
di Java soffrono della stessa confusione confusa, mentre i loro equivalenti in C ++ sono chiari come i tipi incorporati.
Iteratori naturali:
// C++ Random Access iterators
++it ; // move to the next item
--it ; // move to the previous item
it += 5 ; // move to the next 5th item (random access)
value = *it ; // gets the value of the current item
*it = 3.1415 ; // sets the value 3.1415 to the current item
(*it).foo() ; // call method foo() of the current item
// Java ListIterator<E> "bi-directional" iterators
value = it.next() ; // move to the next item & return the value
value = it.previous() ; // move to the previous item & return the value
it.set(3.1415) ; // sets the value 3.1415 to the current item
Funzionari naturali:
// C++ Functors
myFunctorObject("Hello World", 42) ;
// Java Functors ???
myFunctorObject.execute("Hello World", 42) ;
Concatenazione del testo:
// C++ stream handling (with the << operator)
stringStream << "Hello " << 25 << " World" ;
fileStream << "Hello " << 25 << " World" ;
outputStream << "Hello " << 25 << " World" ;
networkStream << "Hello " << 25 << " World" ;
anythingThatOverloadsShiftOperator << "Hello " << 25 << " World" ;
// Java concatenation
myStringBuffer.append("Hello ").append(25).append(" World") ;
Ok, anche in Java puoi usare MyString = "Hello " + 25 + " World" ;
... Ma aspetta un secondo: questo è un sovraccarico dell'operatore, vero? Non è barare ???
:-D
Codice generico?
Gli stessi operandi di modifica del codice generico dovrebbero essere utilizzabili sia per incorporati / primitivi (che non hanno interfacce in Java), oggetti standard (che non potrebbero avere la giusta interfaccia) e oggetti definiti dall'utente.
Ad esempio, calcolando il valore medio di due valori di tipi arbitrari:
// C++ primitive/advanced types
template<typename T>
T getAverage(const T & p_lhs, const T & p_rhs)
{
return (p_lhs + p_rhs) / 2 ;
}
int intValue = getAverage(25, 42) ;
double doubleValue = getAverage(25.25, 42.42) ;
complex complexValue = getAverage(cA, cB) ; // cA, cB are complex
Matrix matrixValue = getAverage(mA, mB) ; // mA, mB are Matrix
// Java primitive/advanced types
// It won't really work in Java, even with generics. Sorry.
Discutere del sovraccarico dell'operatore
Ora che abbiamo visto equi confronti tra il codice C ++ utilizzando il sovraccarico dell'operatore e lo stesso codice in Java, ora possiamo discutere di "sovraccarico dell'operatore" come concetto.
Il sovraccarico dell'operatore esisteva da prima dei computer
Anche al di fuori di informatica, non v'è l'overloading degli operatori: ad esempio, in matematica, operatori come +
, -
, *
, ecc sono sovraccarichi.
Infatti, il significato di +
, -
, *
, ecc cambia a seconda dei tipi degli operandi (i numeri, vettori, funzioni onda quantistica, matrici, ecc).
La maggior parte di noi, nell'ambito dei nostri corsi di scienze, ha imparato molteplici significati per gli operatori, a seconda dei tipi di operandi. Li abbiamo trovati confusi, loro?
Il sovraccarico dell'operatore dipende dai suoi operandi
Questa è la parte più importante del sovraccarico dell'operatore: come in matematica o in fisica, l'operazione dipende dai tipi di operandi.
Quindi, conosci il tipo di operando e conoscerai l'effetto dell'operazione.
Perfino C e Java hanno un sovraccarico dell'operatore (codificato)
In C, il comportamento reale di un operatore cambierà in base ai suoi operandi. Ad esempio, l'aggiunta di due numeri interi è diversa dall'aggiunta di due doppi, o anche un numero intero e un doppio. Esiste anche l'intero dominio aritmetico del puntatore (senza casting, puoi aggiungere a un puntatore un numero intero, ma non puoi aggiungere due puntatori ...).
In Java non esiste l'aritmetica del puntatore, ma qualcuno ha ancora trovato che la concatenazione di stringhe senza l' +
operatore sarebbe abbastanza ridicola da giustificare un'eccezione nel credo "sovraccarico dell'operatore è male".
È solo che tu, come programmatore C (per motivi storici) o Java (per motivi personali , vedi sotto), non puoi fornire il tuo.
In C ++, il sovraccarico dell'operatore non è facoltativo ...
In C ++, il sovraccarico dell'operatore per i tipi incorporati non è possibile (e questa è una buona cosa), ma i tipi definiti dall'utente possono avere sovraccarichi dell'operatore definiti dall'utente .
Come già detto in precedenza, in C ++, e al contrario di Java, i tipi di utenti non sono considerati cittadini di seconda classe della lingua rispetto ai tipi predefiniti. Quindi, se i tipi predefiniti hanno operatori, anche i tipi utente dovrebbero essere in grado di averli.
La verità è che, come il toString()
, clone()
, equals()
metodi sono per Java ( cioè quasi standard simile ), C ++ overload degli operatori è tanto parte di C ++ che diventa naturale come gli operatori C originali, o la prima metodi Java citati.
In combinazione con la programmazione dei modelli, il sovraccarico dell'operatore diventa un modello di progettazione ben noto. In effetti, non puoi andare molto lontano in STL senza usare operatori sovraccaricati e sovraccaricare gli operatori per la tua classe.
... ma non dovrebbe essere abusato
Il sovraccarico dell'operatore dovrebbe cercare di rispettare la semantica dell'operatore. Non sottrarre in un +
operatore (come in "non sottrarre in una add
funzione" o "restituire schifezze in un clone
metodo").
Il sovraccarico del cast può essere molto pericoloso perché può portare ad ambiguità. Quindi dovrebbero davvero essere riservati per casi ben definiti. Per quanto riguarda &&
e ||
, non li sovraccarichi mai a meno che tu non sappia davvero cosa stai facendo, poiché perderai la valutazione del corto circuito che gli operatori nativi &&
e ||
apprezzano.
Quindi ... Ok ... Allora perché non è possibile in Java?
Perché James Gosling ha detto così:
Ho lasciato il sovraccarico dell'operatore come una scelta abbastanza personale perché avevo visto troppe persone abusarne in C ++.
James Gosling. Fonte: http://www.gotw.ca/publications/c_family_interview.htm
Si prega di confrontare il testo di Gosling sopra con quello di Stroustrup di seguito:
Molte decisioni di progettazione in C ++ hanno le mie radici nella mia antipatia per costringere le persone a fare le cose in un modo particolare [...] Spesso, ero tentato di mettere fuorilegge una caratteristica che non mi piaceva, mi sono astenuto dal farlo perché non pensavo di avere il diritto di forzare le mie opinioni sugli altri .
Bjarne Stroustrup. Fonte: The Design and Evolution of C ++ (1.3 General Background)
Il sovraccarico dell'operatore trarrebbe vantaggio da Java?
Alcuni oggetti trarrebbero grandi benefici dal sovraccarico dell'operatore (tipi concreti o numerici, come BigDecimal, numeri complessi, matrici, contenitori, iteratori, comparatori, parser ecc.).
In C ++, puoi trarre vantaggio da questo vantaggio a causa dell'umiltà di Stroustrup. In Java, sei semplicemente fregato a causa della scelta personale di Gosling .
Potrebbe essere aggiunto a Java?
Le ragioni per non aggiungere il sovraccarico dell'operatore ora in Java potrebbero essere un mix di politica interna, allergia alla funzionalità, sfiducia degli sviluppatori (sai, quelli sabotatori che sembrano perseguitare i team Java ...), compatibilità con le precedenti JVM, tempo di scrivere una specifica corretta, ecc.
Quindi non trattenere il respiro in attesa di questa funzione ...
Ma lo fanno in C # !!!
Si...
Mentre questa è ben lungi dall'essere l'unica differenza tra le due lingue, questa non manca mai di divertirmi.
Apparentemente, la gente di C #, con il loro "ogni primitivo è un struct
, e struct
deriva da un oggetto" , ha capito bene al primo tentativo.
Nonostante tutto il FUD contro il sovraccarico dell'operatore definito utilizzato, le seguenti lingue lo supportano: Scala , Dart , Python , F # , C # , D , Algol 68 , Smalltalk , Groovy , Perl 6 , C ++, Ruby , Haskell , MATLAB , Eiffel , Lua , Clojure , Fortran 90 , Swift , Ada , Delphi 2005 ...
Così tante lingue, con così tante filosofie diverse (e talvolta opposte), eppure sono tutte d'accordo su questo punto.
Cibo per la mente...