No entiendo los argumentos en contra de la sobrecarga del operador [cerrado]

82

Acabo de leer uno de los artículos de Joel en el que dice:

  

En general, debo admitir que tengo un poco de miedo a las características del lenguaje que ocultan las cosas . Cuando veas el código

i = j * 5;
     

... en C sabes, al menos, que j se multiplica por cinco y los resultados se almacenan en i.

     

Pero si ves ese mismo fragmento de código en C ++, no sabes nada. Nada. La única forma de saber lo que realmente está sucediendo en C ++ es averiguar qué son los tipos i y j, algo que podría ser declarado en algún otro lugar. Eso es porque j podría ser de un tipo que tiene operator* sobrecargado y hace algo terriblemente ingenioso cuando intentas multiplicarlo.

(Enfatiza lo mío). ¿Te asustan las características del lenguaje que ocultan cosas? ¿Cómo puedes tener miedo de eso? ¿No es la ocultación de las cosas (también conocida como abstracción ) una de las ideas clave de la programación orientada a objetos? Cada vez que llama a un método a.foo(b) , no tiene idea de lo que podría hacer. Tienes que averiguar qué tipos son a y b , algo que podría ser declarado en algún otro lugar. Entonces, ¿deberíamos eliminar la programación orientada a objetos, porque oculta demasiadas cosas del programador?

¿Y en qué se diferencia j * 5 de j.multiply(5) , que podría tener que escribir en un idioma que no admita la sobrecarga de operadores? Una vez más, tendrías que encontrar el tipo de j y mirar dentro del método multiply , porque he aquí que j podría ser de un tipo que tiene un método multiply que hace algo muy ingenioso.

"Muahaha, soy un malvado programador que nombra un método multiply , pero lo que realmente hace es totalmente oscuro y no intuitivo y no tiene absolutamente nada que ver con multiplicar las cosas". ¿Es ese un escenario que debemos tener en cuenta al diseñar un lenguaje de programación? ¡Entonces tenemos que abandonar los identificadores de los lenguajes de programación con el argumento de que pueden ser engañosos!

Si desea saber qué hace un método, puede echar un vistazo a la documentación o echar un vistazo dentro de la implementación. La sobrecarga del operador es solo azúcar sintáctica, y no veo cómo cambia el juego en absoluto.

Por favor, ilumíname.

    
pregunta fredoverflow 10.12.2010 - 12:45
fuente

15 respuestas

31

La abstracción 'oculta' el código para que no tenga que preocuparse por el funcionamiento interno y, a menudo, por lo que no puede cambiarlo, pero la intención no es evitar que lo vea. Simplemente hacemos suposiciones sobre los operadores y, como dijo Joel, podría estar en cualquier lugar. Tener una función de programación que requiera que todos los operadores sobrecargados se establezcan en una ubicación específica puede ayudar a encontrarlo, pero no estoy seguro de que el uso sea más fácil.

No veo hacer que * haga algo que no se parezca mucho a la multiplicación mejor que una función llamada Get_Some_Data que elimina datos.

    
respondido por el JeffO 10.12.2010 - 13:36
fuente
18

En mi humilde opinión, las características del lenguaje como la sobrecarga del operador le dan al programador más poder. Y, como todos sabemos, con gran poder viene una gran responsabilidad. Las características que te dan más poder también te brindan más formas de dispararte en el pie y, obviamente, deben usarse con prudencia.

Por ejemplo, tiene mucho sentido sobrecargar el operador + o * para class Matrix o class Complex . Todos sabrán al instante lo que significa. Por otra parte, para mí el hecho de que + signifique concatenación de cadenas no es del todo obvio, aunque Java hace esto como parte del lenguaje, y STL lo hace para std::string usando la sobrecarga de operadores.

Otro buen ejemplo de cuando la sobrecarga del operador hace que el código sea más claro son los punteros inteligentes en C ++. Desea que los punteros inteligentes se comporten como punteros regulares tanto como sea posible, por lo que tiene mucho sentido sobrecargar los operadores unarios * y -> .

En esencia, la sobrecarga de operadores no es más que otra forma de nombrar una función. Y hay una regla para nombrar las funciones: el nombre debe ser descriptivo, lo que hace inmediatamente obvio lo que hace la función. La misma regla exacta se aplica a la sobrecarga del operador.

    
respondido por el Dima 10.12.2010 - 16:16
fuente
9

En Haskell "+", "-", "*", "/" etc son solo funciones (infijo).

¿Deberías nombrar una función de infijo "más" como en "4 más 2"? Por qué no, si la adición es lo que hace su función. ¿Deberías nombrar tu función "más" "+"? ¿Por qué no?

Creo que el problema con los llamados "operadores" es que se asemejan más a operaciones matemáticas y no hay muchas maneras de interpretarlos y, por lo tanto, hay grandes expectativas sobre lo que hace ese método / función / operador.

EDITAR: aclaré mi punto

    
respondido por el LennyProgrammers 10.12.2010 - 13:18
fuente
7

Basándome en las otras respuestas que he visto, solo puedo concluir que la verdadera objeción a la sobrecarga del operador es el deseo de un código obvio de inmediato.

Esto es trágico por dos razones:

  1. Llevado a su conclusión lógica, el principio de que el código debería ser inmediatamente obvio nos haría a todos todavía codificando en COBOL.
  2. No aprendes de un código que es inmediatamente obvio. Aprende de un código que tiene sentido una vez que se toma un tiempo para pensar cómo funciona.
respondido por el Larry Coleman 10.12.2010 - 17:06
fuente
5

Estoy algo de acuerdo.

Si escribe multiply(j,5) , j podría ser de tipo escalar o de matriz, lo que hace que multiply() sea más o menos complejo, dependiendo de qué j sea. Sin embargo, si abandona la idea de sobrecargar por completo, entonces la función tendría que llamarse multiply_scalar() o multiply_matrix() , lo que haría que sea obvio lo que está sucediendo debajo.

Hay un código donde muchos de nosotros lo preferiríamos de una manera y hay un código donde la mayoría de nosotros lo preferiríamos de otra manera. La mayor parte del código, sin embargo, cae en el terreno intermedio entre esos dos extremos. Lo que prefieras depende de tus antecedentes y preferencias personales.

    
respondido por el sbi 10.12.2010 - 13:15
fuente
4

Veo dos problemas con la sobrecarga del operador.

  1. La sobrecarga cambia la semántica del operador, incluso si el programador no lo pretende. Por ejemplo, cuando sobrecarga && , || o , , pierde los puntos de secuencia que implican las variantes incorporadas de estos operadores (así como el comportamiento de cortocircuito de los operadores lógicos). Por este motivo, es mejor no sobrecargar a estos operadores, incluso si el idioma lo permite.
  2. Algunas personas ven la sobrecarga de operadores como una característica tan agradable, comienzan a usarla en todas partes, incluso si no es la solución adecuada. Esto hace que otras personas reaccionen de forma exagerada en la otra dirección y adviertan contra el uso de la sobrecarga del operador. No estoy de acuerdo con ninguno de los grupos, pero tome el punto medio: la sobrecarga del operador se debe usar con moderación y solo cuando
    • el operador sobrecargado tiene el significado natural tanto para los expertos en dominios como para los expertos en software. Si esos dos grupos no están de acuerdo con el significado natural para el operador, no lo sobrecargue.
    • para el tipo (s) involucrado, no hay un significado natural para el operador y el contexto inmediato (preferiblemente la misma expresión, pero no más de unas pocas líneas) siempre deja claro cuál es el significado del operador. Un ejemplo de esta categoría sería operator<< para transmisiones.
respondido por el Bart van Ingen Schenau 10.12.2010 - 16:02
fuente
3

Según mi experiencia personal, la forma Java de permitir múltiples métodos, pero no sobrecargar al operador, significa que cada vez que vea a un operador, sepa exactamente lo que hace.

No tiene que ver si * invoca un código extraño, pero sepa que es una multiplicación y se comporta exactamente de la manera definida por la Especificación del lenguaje Java. Esto significa que puede concentrarse en el comportamiento real en lugar de descubrir todas las cosas definidas por el programador.

En otras palabras, prohibir la sobrecarga del operador es un beneficio para el lector , no para el escritor , y por lo tanto hace que los programas sean más fáciles de mantener.

    
respondido por el user1249 10.12.2010 - 13:20
fuente
3

Una diferencia entre la sobrecarga a * b y llamar a multiply(a,b) es que esta última puede ser fácilmente detectada. Si la función multiply no está sobrecargada para diferentes tipos, entonces puede averiguar exactamente qué va a hacer la función, sin tener que rastrear los tipos de a y b .

Linus Torvalds tiene un argumento interesante Sobre la sobrecarga del operador. En algo como el desarrollo del kernel de Linux, donde la mayoría de los cambios se envían a través de parches a través del correo electrónico, es importante que los mantenedores puedan entender lo que hará un parche con solo unas pocas líneas de contexto alrededor de cada cambio. Si las funciones y los operadores no están sobrecargados, entonces el parche se puede leer más fácilmente en un contexto independiente, ya que no tiene que revisar el archivo modificado para averiguar qué son todos los tipos y verificar los operadores sobrecargados.

    
respondido por el Scott Wales 10.12.2010 - 14:19
fuente
2

Sospecho que tiene algo que ver con romper las expectativas. Estoy acostumbrado a C ++, está acostumbrado a que el comportamiento del operador no sea dictado completamente por el idioma y no se sorprenderá cuando un operador haga algo extraño. Si está acostumbrado a los idiomas que no tienen esa característica, y luego ve el código C ++, trae consigo las expectativas de esos otros idiomas, y puede sorprenderse al descubrir que un operador sobrecargado hace algo raro.

Personalmente creo que hay una diferencia. Cuando puede cambiar el comportamiento de la sintaxis incorporada en el lenguaje, se vuelve más opaco para razonar. Los lenguajes que no permiten la meta-programación son sintácticamente menos poderosos, pero conceptualmente son más fáciles de entender.

    
respondido por el Joeri Sebrechts 10.12.2010 - 12:56
fuente
2

Creo que la sobrecarga de operadores matemáticos no es el problema real con la sobrecarga de operadores en C ++. Creo que los operadores de sobrecarga que no deberían confiar en el contexto de la expresión (es decir, el tipo) es "malo". P.ej. sobrecarga , [ ] ( ) -> ->* new delete o incluso el unario * . Tiene un cierto conjunto de expectativas de aquellos operadores que nunca deben cambiar.

    
respondido por el Allon Guralnek 10.12.2010 - 13:46
fuente
2

Entiendo perfectamente que no te gusta el argumento de Joel sobre la ocultación. Yo tampoco. De hecho, es mucho mejor usar '+' para cosas como tipos numéricos incorporados o para sus propios como, por ejemplo, matriz. Admito que esto es limpio y elegante para poder multiplicar dos matrices con el '*' en lugar de '.multiply ()'. Y, después de todo, tenemos el mismo tipo de abstracción en ambos casos.

Lo que duele aquí es la legibilidad de su código. En los casos de la vida real, no en el ejemplo académico de la multiplicación de matrices. Especialmente si su idioma permite definir operadores que no están inicialmente presentes en el núcleo del idioma, por ejemplo, =:= . Muchas preguntas adicionales surgen en este punto. ¿De qué se trata ese maldito operador? Quiero decir, ¿cuál es la precedencia de esa cosa? ¿Qué es la asociatividad? ¿En qué orden se ejecuta realmente el a =:= b =:= c ?

Eso ya es un argumento en contra de la sobrecarga del operador. ¿Todavía no está convencido? ¿Revisar las reglas de precedencia no le llevó más de 10 segundos? Ok, vamos más allá.

Si comienza a utilizar un idioma que permite la sobrecarga de operadores, por ejemplo, el popular cuyo nombre comienza con 'S', rápidamente descubrirá que a los diseñadores de bibliotecas les encanta anular los operadores. Por supuesto, están bien educados, siguen las mejores prácticas (no hay cinismo aquí) y todas sus API tienen mucho sentido cuando las vemos por separado.

Ahora imagine que tiene que usar algunas API que hacen un uso intensivo de los operadores que se sobrecargan juntos en una sola pieza de código. O incluso mejor, tienes que leer un código legado como ese. Esto es cuando la sobrecarga del operador realmente apesta. Básicamente, si hay muchos operadores sobrecargados en un lugar, pronto comenzarán a mezclarse con los otros caracteres no alfanuméricos en su código de programa. Se mezclarán con caracteres no alfanuméricos que no son realmente operadores, sino más bien algunos elementos gramaticales del lenguaje más fundamentales que definen cosas como bloques y ámbitos, declaraciones de control de flujo de formas o denotan algunas cosas meta. Deberá colocar los anteojos y acercar sus ojos 10 cm a la pantalla LCD para comprender el desorden visual.

    
respondido por el akosicki 16.04.2015 - 19:29
fuente
1

En general, evito usar la sobrecarga de operadores de manera no intuitiva. Es decir, si tengo una clase numérica, la sobrecarga * es aceptable (y se recomienda). Sin embargo, si tengo un empleado de clase, ¿qué haría la sobrecarga *? En otras palabras, sobrecargue a los operadores de manera intuitiva que facilite su lectura y comprensión.

Aceptable / Animado:

class Complex
{
public:
    double r;
    double i;

    Complex operator*(const Compex& rhs)
    {
        Complex result;
        result.r = (r * rhs.r) - (i * rhs.i);
        result.i = (r * rhs.i) + (i * rhs.r);
        return result;
    }
};

No es aceptable:

class Employee
{
public:
    std::string name;
    std::string address;
    std::string phone_number;

    Employee operator* (const Employee& e)
    {
        // what the hell do I do here??
    }
};
    
respondido por el Zac Howland 10.12.2010 - 21:26
fuente
1

Además de lo que ya se ha dicho aquí, hay un argumento más en contra de la sobrecarga del operador. De hecho, si escribes + , esto es algo obvio que te refieres a la adición de algo a algo. Pero este no es siempre el caso.

C ++ en sí mismo proporciona un gran ejemplo de tal caso. ¿Cómo se debe leer stream << 1 ? ¿Corriente cambiada a la izquierda por 1? No es obvio en absoluto a menos que sepa explícitamente que < < en C ++ también escribe a la secuencia. Sin embargo, si esta operación se implementara como un método, ningún desarrollador sano escribiría o.leftShift(1) , sería algo así como o.write(1) .

La conclusión es que al hacer que la sobrecarga del operador no esté disponible, el lenguaje hace que los programadores piensen en los nombres de las operaciones. Incluso si el nombre elegido no es perfecto, es aún más difícil interpretar mal un nombre que un signo.

    
respondido por el Malcolm 08.10.2011 - 14:04
fuente
1

En comparación con los métodos detallados, los operadores son más cortos, pero tampoco requieren paréntesis. Los paréntesis son relativamente inconvenientes para el tipo. Y hay que equilibrarlos. En total, cualquier llamada a un método requiere tres caracteres de ruido normal en comparación con un operador. Esto hace que el uso de operadores sea muy, muy tentador. ¿Por qué otra persona querría esto: cout << "Hello world" ?

El problema con la sobrecarga es que la mayoría de los programadores son increíblemente perezosos y la mayoría de los programadores no pueden darse el lujo de serlo.

Lo que impulsa a los programadores de C ++ a abusar de la sobrecarga del operador no es su presencia, sino la ausencia de una forma más ordenada de realizar llamadas a métodos. Y las personas no solo temen la sobrecarga del operador porque es posible, sino porque ya está hecho.
Tenga en cuenta que, por ejemplo, en Ruby y Scala, nadie teme la sobrecarga del operador. Aparte del hecho de que el uso de operadores no es realmente más corto que los métodos, otra razón es que Ruby limita la sobrecarga del operador a un mínimo razonable, mientras que Scala le permite declarar sus propios operadores haciendo que evitar la colisión sea trivial.

    
respondido por el back2dos 08.10.2011 - 23:08
fuente
0

La razón por la que la Sobrecarga del Operador da miedo, es porque hay una gran cantidad de programadores que nunca PENSARÁN que * no significa simplemente "multiplicar", mientras que un método como foo.multiply(bar) al menos señala instantáneamente Ese programador que alguien escribió un método de multiplicación personalizado. En ese momento se preguntaban por qué y se iban a investigar.

He trabajado con "buenos programadores" que estaban en posiciones de alto nivel que crearían métodos llamados "Valores de comparación" que tomarían 2 argumentos, y aplicarían los valores de uno a otro y devolverían un valor booleano. O un método llamado "LoadTheValues" que iría a la base de datos para otros 3 objetos, obtendría valores, realizará cálculos, modificará this y lo guardará en la base de datos.

Si estoy trabajando en un equipo con ese tipo de programadores, lo sé al instante para investigar las cosas en las que han trabajado. Si sobrecargaron a un operador, no tengo forma de saber que lo hicieron, excepto para suponer que lo hicieron e ir a buscar.

En un mundo perfecto, o en un equipo con programadores perfectos, la sobrecarga de operadores es probablemente una herramienta fantástica. Sin embargo, todavía tengo que trabajar en un equipo de programadores perfectos, así que por eso da miedo.

    
respondido por el James P. Wright 08.10.2011 - 22:28
fuente

Lea otras preguntas en las etiquetas