Hay muchas publicaciones quejándose de la sobrecarga del operador.
Sentí que tenía que aclarar los conceptos de "sobrecarga del operador", ofreciendo un punto de vista alternativo sobre este concepto.
Código de ofuscación?
Este argumento es una falacia.
La ofuscación es posible en todos los idiomas ...
Es tan fácil ofuscar el código en C o Java a través de funciones / métodos como lo es en C ++ a través de sobrecargas del operador:
// 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 ;
}
... Incluso en las interfaces estándar de Java
Para otro ejemplo, veamos la Cloneable
interfaz en Java:
Se supone que debe clonar el objeto que implementa esta interfaz. Pero podrías mentir. Y crea un objeto diferente. De hecho, esta interfaz es tan débil que podría devolver otro tipo de objeto por completo, solo por el gusto de hacerlo:
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
Como Cloneable
se puede abusar / ofuscar la interfaz, ¿debería prohibirse por los mismos motivos que se supone que es la sobrecarga del operador de C ++?
Podríamos sobrecargar el toString()
método de una MyComplexNumber
clase para que devuelva la hora en cadena del día. ¿Debería toString()
prohibirse también la sobrecarga? Podríamos sabotear MyComplexNumber.equals
para que devuelva un valor aleatorio, modifique los operandos ... etc. etc. etc.
En Java, como en C ++, o cualquier otro lenguaje, el programador debe respetar un mínimo de semántica al escribir código. Esto significa implementar una add
función que agregue, y un Cloneable
método de implementación que clone, y un ++
operador que incremente.
¿Qué es lo que ofusca de todos modos?
Ahora que sabemos que el código puede ser saboteado incluso a través de los prístinos métodos de Java, podemos preguntarnos sobre el uso real de la sobrecarga de operadores en C ++.
Notación clara y natural: ¿métodos frente a sobrecarga del operador?
Compararemos a continuación, para diferentes casos, el "mismo" código en Java y C ++, para tener una idea de qué tipo de estilo de codificación es más claro.
Comparaciones naturales:
// 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 ;
Tenga en cuenta que A y B pueden ser de cualquier tipo en C ++, siempre que se proporcionen las sobrecargas del operador. En Java, cuando A y B no son primitivas, el código puede volverse muy confuso, incluso para objetos primitivos (BigInteger, etc.) ...
Arreglos naturales / contenedores y suscriptores:
// 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
En Java, vemos que para que cada contenedor haga lo mismo (acceder a su contenido a través de un índice o identificador), tenemos una forma diferente de hacerlo, lo cual es confuso.
En C ++, cada contenedor utiliza la misma forma de acceder a su contenido, gracias a la sobrecarga del operador.
Manipulación natural de tipos avanzados
Los ejemplos a continuación utilizan un Matrix
objeto, que se encuentra utilizando los primeros enlaces encontrados en Google para " objeto Matrix Java " y " Objeto 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
Y esto no se limita a las matrices. Las clases BigInteger
y BigDecimal
de Java sufren de la misma verbosidad confusa, mientras que sus equivalentes en C ++ son tan claros como los tipos incorporados.
Iteradores naturales:
// 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
Functores naturales:
// C++ Functors
myFunctorObject("Hello World", 42) ;
// Java Functors ???
myFunctorObject.execute("Hello World", 42) ;
Concatenación de texto:
// 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, en Java también puedes usar MyString = "Hello " + 25 + " World" ;
... Pero, espera un segundo: esto es sobrecarga del operador, ¿no? ¿No es trampa?
:-RE
Código genérico?
Los mismos operandos modificadores del código genérico deberían ser utilizables tanto para elementos integrados / primitivos (que no tienen interfaces en Java), objetos estándar (que no podrían tener la interfaz correcta) y objetos definidos por el usuario.
Por ejemplo, calcular el valor promedio de dos valores de tipos arbitrarios:
// 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.
Discutir sobrecarga del operador
Ahora que hemos visto comparaciones justas entre el código C ++ usando la sobrecarga del operador y el mismo código en Java, ahora podemos discutir la "sobrecarga del operador" como un concepto.
La sobrecarga del operador existía desde antes de las computadoras
Incluso fuera de la informática, no hay sobrecarga de operadores: Por ejemplo, en matemáticas, operadores como +
, -
, *
, etc., están sobrecargados.
De hecho, la significación de +
, -
, *
, etc. cambios dependiendo de los tipos de los operandos (Numerics, vectores, funciones de onda cuántica, matrices, etc.).
La mayoría de nosotros, como parte de nuestros cursos de ciencias, aprendimos múltiples significados para los operadores, dependiendo de los tipos de operandos. ¿Los encontramos confusos, ellos?
La sobrecarga del operador depende de sus operandos
Esta es la parte más importante de la sobrecarga del operador: como en matemáticas o en física, la operación depende de los tipos de sus operandos.
Entonces, conozca el tipo de operando, y sabrá el efecto de la operación.
Incluso C y Java tienen una sobrecarga del operador (codificada)
En C, el comportamiento real de un operador cambiará de acuerdo con sus operandos. Por ejemplo, agregar dos enteros es diferente de agregar dos dobles, o incluso uno entero y uno doble. Incluso existe todo el dominio aritmético del puntero (sin conversión, puede agregar a un puntero un número entero, pero no puede agregar dos punteros ...).
En Java, no existe una aritmética de puntero, pero alguien todavía encontró la concatenación de cadenas sin el +
operador sería lo suficientemente ridículo como para justificar una excepción en el credo "la sobrecarga del operador es malo".
Es solo que usted, como codificador C (por razones históricas) o Java (por razones personales , ver más abajo), no puede proporcionar el suyo.
En C ++, la sobrecarga del operador no es opcional ...
En C ++, la sobrecarga del operador para los tipos integrados no es posible (y esto es algo bueno), pero los tipos definidos por el usuario pueden tener sobrecargas del operador definidas por el usuario .
Como ya se dijo anteriormente, en C ++, y al contrario de Java, los tipos de usuario no se consideran ciudadanos de segunda clase del lenguaje, en comparación con los tipos incorporados. Por lo tanto, si los tipos incorporados tienen operadores, los tipos de usuarios también deberían poder tenerlos.
La verdad es que, como el toString()
, clone()
, equals()
métodos son para Java ( es decir, cuasi-estándar similar ), C ++ sobrecarga de operadores es tanto parte de C ++ que se convierte en tan natural como los operadores C originales, o la antes métodos Java mencionados.
En combinación con la programación de plantillas, la sobrecarga del operador se convierte en un patrón de diseño bien conocido. De hecho, no puede llegar muy lejos en STL sin usar operadores sobrecargados y sobrecargar operadores para su propia clase.
... pero no debe ser abusado
La sobrecarga del operador debe esforzarse por respetar la semántica del operador. No reste en un +
operador (como en "no restar en una add
función" o "devolver basura en un clone
método").
La sobrecarga de yeso puede ser muy peligrosa porque puede generar ambigüedades. Por lo tanto, realmente deberían reservarse para casos bien definidos. En cuanto a &&
y ||
, no siempre sobrecargarlos menos que realmente sepa lo que está haciendo, ya que si no se pierden la evaluación del cortocircuito que los operadores nativos &&
y ||
disfrutar.
Entonces ... Ok ... ¿Entonces por qué no es posible en Java?
Porque James Gosling lo dijo:
Dejé la sobrecarga del operador como una opción bastante personal porque había visto a muchas personas abusar de ella en C ++.
James Gosling Fuente: http://www.gotw.ca/publications/c_family_interview.htm
Compare el texto de Gosling arriba con el de Stroustrup a continuación:
Muchos de C ++ decisiones de diseño tienen sus raíces en mi aversión por obligar a la gente a hacer las cosas de cierta manera particular, [...] A menudo, tuve la tentación de prohibir una característica que personalmente no le gustaba, me abstenido de hacerlo porque yo no creía que tuviera El derecho a forzar mis puntos de vista sobre los demás .
Bjarne Stroustrup. Fuente: El diseño y la evolución de C ++ (1.3 Antecedentes generales)
¿La sobrecarga del operador beneficiaría a Java?
Algunos objetos se beneficiarían enormemente de la sobrecarga del operador (tipos concretos o numéricos, como BigDecimal, números complejos, matrices, contenedores, iteradores, comparadores, analizadores, etc.).
En C ++, puede beneficiarse de este beneficio debido a la humildad de Stroustrup. En Java, simplemente estás jodido debido a la elección personal de Gosling .
¿Se podría agregar a Java?
Las razones para no agregar la sobrecarga de operadores ahora en Java podrían ser una combinación de políticas internas, alergia a la característica, desconfianza de los desarrolladores (ya sabes, los saboteadores que parecen perseguir a los equipos de Java ...), compatibilidad con las JVM anteriores, hora de escribir una especificación correcta, etc.
Así que no contengas la respiración esperando esta característica ...
¡Pero lo hacen en C #!
Si...
Si bien esto está lejos de ser la única diferencia entre los dos idiomas, este nunca deja de divertirme.
Aparentemente, la gente de C #, con su "todo primitivo es un struct
, y un struct
deriva del Objeto" , acertó al primer intento.
A pesar de todo el FUD contra la sobrecarga del operador definido usado, los siguientes idiomas lo admiten: 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 ...
Tantos lenguajes, con tantas filosofías diferentes (y a veces opuestas) y, sin embargo, todos están de acuerdo en ese punto.
Comida para el pensamiento...