Sobrecarga de operadores

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 9 de julio de 2018; las comprobaciones requieren 25 ediciones .

La sobrecarga de operadores en programación  es una de las formas de implementar el polimorfismo , que consiste en la posibilidad de la existencia simultánea en un mismo ámbito de varias opciones diferentes para utilizar operadores que tienen el mismo nombre, pero difieren en los tipos de parámetros a los que están sujetos. aplicado.

Terminología

El término " sobrecarga " es un papel de calco de la palabra inglesa sobrecarga . Tal traducción apareció en libros sobre lenguajes de programación en la primera mitad de la década de 1990. En las publicaciones del período soviético, mecanismos similares se denominaron redefinición o redefinición , operaciones superpuestas .

Razones para

A veces existe la necesidad de describir y aplicar operaciones a los tipos de datos creados por el programador que tienen un significado equivalente a los que ya están disponibles en el lenguaje. Un ejemplo clásico es la biblioteca para trabajar con números complejos . Ellos, como los tipos numéricos ordinarios, admiten operaciones aritméticas, y sería natural crear para este tipo de operaciones "más", "menos", "multiplicar", "dividir", denotándolos con los mismos signos de operación que para otros números. tipos La prohibición del uso de elementos definidos en el lenguaje obliga a la creación de muchas funciones con nombres como ComplexPlusComplex, IntegerPlusComplex, ComplexMinusFloat, etc.

Cuando se aplican operaciones del mismo significado a operandos de diferentes tipos, se les obliga a tener nombres diferentes. La imposibilidad de utilizar funciones con el mismo nombre para diferentes tipos de funciones lleva a la necesidad de inventar diferentes nombres para lo mismo, lo que crea confusión e incluso puede dar lugar a errores. Por ejemplo, en el lenguaje C clásico, hay dos versiones de la función de biblioteca estándar para encontrar el módulo de un número: abs() y fabs() - la primera es para un argumento entero, la segunda para uno real. Esta situación, combinada con una verificación de tipo C débil, puede llevar a un error difícil de encontrar: si un programador escribe abs(x) en el cálculo, donde x es una variable real, entonces algunos compiladores generarán código sin previo aviso que convierta x en un número entero descartando las partes fraccionarias y calcule el módulo a partir del número entero resultante.

En parte, el problema se resuelve mediante la programación de objetos: cuando los nuevos tipos de datos se declaran como clases, las operaciones sobre ellos se pueden formalizar como métodos de clase, incluidos los métodos de clase del mismo nombre (ya que los métodos de diferentes clases no tienen que tener nombres diferentes), pero, en primer lugar, tal forma de diseño de operaciones en valores de diferentes tipos es inconveniente y, en segundo lugar, no resuelve el problema de crear nuevos operadores.

Las herramientas que le permiten expandir el lenguaje, complementarlo con nuevas operaciones y construcciones sintácticas (y la sobrecarga de operaciones es una de esas herramientas, junto con objetos, macros, funcionales, cierres) lo convierten en un metalenguaje  , una herramienta para describir lenguajes. centrado en tareas específicas. Con su ayuda, es posible construir una extensión de lenguaje para cada tarea específica que sea más apropiada para ella, lo que permitirá describir su solución de la forma más natural, comprensible y simple. Por ejemplo, en una aplicación para operaciones de sobrecarga: crear una biblioteca de tipos matemáticos complejos (vectores, matrices) y describir las operaciones con ellos en una forma natural, "matemática", crea un "lenguaje para operaciones vectoriales", en el que la complejidad de Los cálculos están ocultos, y es posible describir la solución de problemas en términos de operaciones vectoriales y matriciales, centrándose en la esencia del problema, no en la técnica. Fue por estas razones que tales medios alguna vez se incluyeron en el lenguaje Algol-68 .

Mecanismo de sobrecarga

Implementación

La sobrecarga de operadores implica la introducción de dos características interrelacionadas en el lenguaje: la capacidad de declarar varios procedimientos o funciones con el mismo nombre en el mismo ámbito y la capacidad de describir sus propias implementaciones de operadores binarios (es decir, los signos de operaciones, generalmente escrito en notación infija, entre operandos). Básicamente, su implementación es bastante simple:

Sobrecarga de operadores en C++

Hay cuatro tipos de sobrecarga de operadores en C++:

  1. Sobrecarga de operadores ordinarios + - * / % ˆ & | ~ ! = < > += -= *= /= %= ˆ= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> ( ) <=> [ ]
  2. Sobrecarga de operadores de conversión de tipos
  3. Sobrecarga de operadores de asignación '''nueva''' y '''borrar''' para objetos en memoria.
  4. Operador de sobrecarga "" literales
Operadores ordinarios

Es importante recordar que la sobrecarga mejora el idioma, no cambia el idioma, por lo que no puede sobrecargar operadores para tipos integrados. No puede cambiar la precedencia y la asociatividad (de izquierda a derecha o de derecha a izquierda) de los operadores. No puede crear sus propios operadores y sobrecargar algunos de los integrados: :: . .* ?: sizeof typeid. Además, los operadores && || ,pierden sus propiedades únicas cuando se sobrecargan: pereza para los dos primeros y precedencia para una coma (el orden de las expresiones entre comas se define estrictamente como asociativo a la izquierda, es decir, de izquierda a derecha). El operador ->debe devolver un puntero o un objeto (por copia o referencia).

Los operadores se pueden sobrecargar como funciones independientes y como funciones miembro de una clase. En el segundo caso, el argumento izquierdo del operador es siempre el objeto *este. Los operadores = -> [] ()solo se pueden sobrecargar como métodos (funciones miembro), no como funciones.

Puede hacer que escribir código sea mucho más fácil si sobrecarga los operadores en un orden determinado. Esto no solo acelerará la escritura, sino que también evitará que dupliques el mismo código. Consideremos una sobrecarga usando el ejemplo de una clase que es un punto geométrico en un espacio vectorial bidimensional:

punto de clase _ { int x , y ; público : Punto ( int x , int xx ) : x ( x ), y ( xx ) {} // El constructor predeterminado se ha ido. // Los nombres de los argumentos de los constructores pueden ser los mismos que los nombres de los campos de clase. }
  • Operadores de asignación de copiar y mover operator=
    Vale la pena considerar que, por defecto, C++ crea cinco funciones básicas además del constructor. Por lo tanto, la sobrecarga de copiar y mover operadores de asignación es mejor dejarla en manos del compilador o implementarla usando el modismo Copiar e intercambiar .
  • Operadores aritméticos combinados += *= -= /= %=, etc.
    Si queremos implementar operadores aritméticos binarios ordinarios, será más conveniente implementar primero este grupo de operadores.Punto y punto :: operador += ( const Punto y rhs ) { x += rhs . x ; y += der . y ; devolver * esto ; }
El operador devuelve un valor por referencia, esto le permite escribir tales construcciones:(a += b) += c;
  • Operadores aritméticos + * - / %
    Para deshacernos de la repetición de código, usemos nuestro operador combinado. El operador no modifica el objeto, por lo que devuelve un nuevo objeto.const Punto Punto :: operador + ( const Punto & rhs ) const { punto de retorno ( * este ) += rhs ; }
El operador devuelve un valor constante. Esto nos protegerá de escribir construcciones de este tipo (a + b) = c;. Por otro lado, para clases que son costosas de copiar, es mucho más rentable devolver un valor de una copia no constante, es decir, : MyClass MyClass::operator+(const MyClass& rhs) const;. Luego, con dicho registro x = y + z;, se llamará al constructor de movimiento, no al constructor de copia.
  • Operadores aritméticos unarios + -
    Los operadores más y menos unarios no toman argumentos cuando están sobrecargados. No cambian el objeto en sí (en nuestro caso), sino que devuelven un nuevo objeto modificado. También debe sobrecargarlos si sus contrapartes binarias están sobrecargadas.
Punto Punto :: operador + () { puntoRetorno ( * este ) ; } Punto Punto :: operador - () { punto tmp ( * esto ); tmp . x *=- 1 ; tmp . y *= -1 ; volver tmp ; }
  • Operadores de comparación == != < <= > >=
    Lo primero que hay que hacer es sobrecargar los operadores de igualdad y desigualdad. El operador de desigualdad utilizará el operador de igualdad.
bool Punto :: operador == ( const Punto & rhs ) const { return ( esto -> x == rhs . x && esto -> y == rhs . y ); } bool Punto :: operador != ( const Punto & rhs ) const { volver ! ( * esto == derecho ); } A continuación, se sobrecargan los operadores < y >, y luego sus contrapartes no estrictas, utilizando los operadores previamente sobrecargados. Para los puntos en geometría, dicha operación no está definida, por lo que en este ejemplo no tiene sentido sobrecargarlos.
  • Operadores bit a bit <<= >>= &= |= ^= и << >> & | ^ ~
    Están sujetos a los mismos principios que los operadores aritméticos. En algunas clases, el uso de una máscara de bits será útil std::bitset. Nota: El operador & tiene una contraparte unaria y se usa para tomar una dirección; por lo general no está sobrecargado.
  • Operadores lógicos && ||
    Estos operadores perderán sus propiedades únicas de pereza cuando se sobrecarguen.
  • Incrementar y decrementar ++ --
    C++ le permite sobrecargar tanto el incremento como el decremento de postfijo y prefijo. Considere un incremento:
Point & Point :: operador ++ () { // prefijo x ++ ; y ++ ; devolver * esto ; } Point Point :: operator ++ ( int ) { //postfix Point tmp ( x , y , i ); ++ ( * esto ); volver tmp ; } Tenga en cuenta que la función miembro operator++(int) toma un valor de tipo int, pero este argumento no tiene nombre. C++ le permite crear tales funciones. Podemos darle (al argumento) un nombre y aumentar los valores de los puntos por este factor, sin embargo, en forma de operador, este argumento será cero por defecto y solo se puede llamar en estilo funcional:A.operator++(5);
  • El operador () no tiene restricciones en el tipo de devolución y tipos/número de argumentos, y le permite crear funtores .
  • Un operador para pasar una clase al flujo de salida. Implementado como una función separada, no como una función miembro. En la clase, esta función está marcada como amigable.friend std::ostream& operator<<(const ostream& s, const Point& p);

Otros operadores no están sujetos a ninguna directriz general de sobrecarga.

Tipo de conversiones

Las conversiones de tipo le permiten especificar las reglas para convertir nuestra clase a otros tipos y clases. También puede especificar el especificador explícito, que permitirá la conversión de tipos solo si el programador lo especificó explícitamente (por ejemplo , static_cast<Point3>(Point(2,3)); ). Ejemplo:

Punto :: operador bool () const { devuelve esto -> x != 0 || esto -> y != 0 ; } Operadores de asignación y desasignación

Los operadores new new[] delete delete[]pueden estar sobrecargados y pueden tomar cualquier número de argumentos. Además, los operadores new и new[]deben tomar un argumento de tipo como primer argumento std::size_ty devolver un valor de tipo void *, y los operadores deben tomar el delete delete[]primero void *y no devolver nada ( void). Estos operadores se pueden sobrecargar tanto para funciones como para clases concretas.

Ejemplo:

void * MiClase :: operador nuevo ( std :: size_t s , int a ) { vacío * p = malloc ( s * a ); si ( p == punto nulo ) lanzar "¡No hay memoria libre!" ; devuelve p ; } // ... // Llamada: MiClase * p = new ( 12 ) MiClase ;


Literales personalizados

Los literales personalizados existen desde el undécimo estándar de C++. Los literales se comportan como funciones regulares. Pueden ser calificadores en línea o constexpr . Es deseable que el literal comience con un carácter de subrayado, ya que puede haber un conflicto con estándares futuros. Por ejemplo, el literal i ya pertenece a los números complejos de std::complex.

Los literales pueden tomar sólo uno de los siguientes tipos: const char * , unsigned long long int , long double , char , wchar_t , char16_t , char32_t. Basta con sobrecargar el literal solo para el tipo const char * . Si no se encuentra un candidato más adecuado, se llamará a un operador con ese tipo. Un ejemplo de conversión de millas a kilómetros:

constexpr int operador "" _mi ( int largo largo sin signo i ) { retornar 1.6 * i ;} operador doble constexpr "" _mi ( long double i ) { retornar 1.6 * i ;}

Los literales de cadena toman un segundo argumento std::size_ty uno de los primeros: const char * , const wchar_t *, const char16_t * , const char32_t *. Los literales de cadena se aplican a las entradas entre comillas dobles.

C++ tiene un literal R de cadena de prefijo incorporado que trata todos los caracteres entre comillas como caracteres regulares y no interpreta ciertas secuencias como caracteres especiales. Por ejemplo, dicho comando std::cout << R"(Hello!\n)"mostrará Hello!\n.

Ejemplo de implementación en C#

La sobrecarga de operadores está estrechamente relacionada con la sobrecarga de métodos. Un operador está sobrecargado con la palabra clave Operador, que define un "método de operador", que, a su vez, define la acción del operador con respecto a su clase. Hay dos formas de métodos de operador (operador): uno para operadores unarios , el otro para operadores binarios . A continuación se muestra la forma general para cada variación de estos métodos.

// forma general de sobrecarga de operador unario. public static return_type operator op ( operando de tipo_parámetro ) { // operaciones } // Forma general de sobrecarga de operadores binarios. public static return_type operator op ( parametro_tipo1 operando1 , parametro_tipo2 operando2 ) { // operaciones }

Aquí, en lugar de "op", se sustituye un operador sobrecargado, por ejemplo + o /; y "return_type" denota el tipo específico de valor devuelto por la operación especificada. Este valor puede ser de cualquier tipo, pero a menudo se especifica que sea del mismo tipo que la clase para la que se sobrecarga el operador. Esta correlación facilita el uso de operadores sobrecargados en expresiones. Para los operadores unarios, el operando denota el operando que se está pasando, y para los operadores binarios, lo mismo se denota por "operando1 y operando2". Tenga en cuenta que los métodos de operador deben ser de ambos tipos, públicos y estáticos. El tipo de operando de los operadores unarios debe ser el mismo que el de la clase para la que se sobrecarga el operador. Y en los operadores binarios, al menos uno de los operandos debe ser del mismo tipo que su clase. Por lo tanto, C# no permite que ningún operador se sobrecargue en objetos que aún no se han creado. Por ejemplo, la asignación del operador + no se puede anular para elementos de tipo int o string . No puede usar el modificador ref o out en los parámetros del operador. [una]

Opciones y problemas

La sobrecarga de procedimientos y funciones al nivel de una idea general, por regla general, no es difícil de implementar ni de comprender. Sin embargo, incluso en él hay algunas "trampas" que deben ser consideradas. Permitir la sobrecarga de operadores crea muchos más problemas tanto para el implementador del lenguaje como para el programador que trabaja en ese lenguaje.

Problema de identificación

El primer problema es la dependencia del contexto . Es decir, la primera pregunta que enfrenta un desarrollador de un traductor de lenguaje que permite la sobrecarga de procedimientos y funciones es: ¿cómo elegir entre los procedimientos del mismo nombre el que se debe aplicar en este caso particular? Todo está bien si existe una variante del procedimiento cuyos tipos de parámetros formales coincidan exactamente con los tipos de los parámetros reales utilizados en esta llamada. Sin embargo, en casi todos los lenguajes, existe cierto grado de libertad en el uso de tipos, asumiendo que el compilador en ciertas situaciones convierte automáticamente (emite) tipos de datos de manera segura. Por ejemplo, en operaciones aritméticas con argumentos reales y enteros, el tipo entero generalmente se convierte automáticamente en tipo real y el resultado es real. Supongamos que hay dos variantes de la función de suma:

suma int(int a1, int a2); agregar flotante (flotante a1, flotante a2);

¿Cómo debe manejar el compilador la expresión y = add(x, i)donde x es de tipo float e i es de tipo int? Obviamente no hay una coincidencia exacta. Hay dos opciones: ya sea y=add_int((int)x,i)o como (aquí , la primera y la segunda versión de la función se indican con y=add_flt(x, (float)i)los nombres add_inty respectivamente).add_flt

Surge la pregunta: ¿debería el compilador permitir este uso de funciones sobrecargadas y, de ser así, sobre qué base elegirá la variante particular utilizada? En particular, en el ejemplo anterior, ¿debería el traductor considerar el tipo de variable y al elegir? Cabe señalar que la situación dada es la más simple. Pero son posibles casos mucho más complicados, que se ven agravados por el hecho de que no solo los tipos incorporados se pueden convertir de acuerdo con las reglas del lenguaje, sino que también las clases declaradas por el programador, si tienen relaciones de parentesco, se pueden convertir de de uno a otro. Hay dos soluciones para este problema:

  • Prohibir la identificación inexacta en absoluto. Requerir que para cada par particular de tipos haya una variante exactamente adecuada del procedimiento u operación sobrecargada. Si no existe tal opción, el compilador debería arrojar un error. En este caso, el programador debe aplicar una conversión explícita para convertir los parámetros reales en el conjunto de tipos deseado. Este enfoque es inconveniente en lenguajes como C ++, que permiten una gran libertad en el manejo de tipos, ya que conduce a una diferencia significativa en el comportamiento de los operadores integrados y sobrecargados (las operaciones aritméticas se pueden aplicar a números ordinarios sin pensar, sino a otros tipos, solo con conversión explícita) o al surgimiento de una gran cantidad de opciones para operaciones.
  • Establezca ciertas reglas para elegir el "ajuste más cercano". Por lo general, en esta variante, el compilador elige aquellas de las variantes cuyas llamadas se pueden obtener de la fuente solo mediante conversiones de tipo seguras (sin pérdida de información), y si hay varias de ellas, puede elegir en función de qué variante requiere menos. tales conversiones. Si el resultado deja más de una posibilidad, el compilador arroja un error y requiere que el programador especifique explícitamente la variante.
Problemas específicos de sobrecarga de operaciones

A diferencia de los procedimientos y funciones, las operaciones infijas de los lenguajes de programación tienen dos propiedades adicionales que afectan significativamente su funcionalidad: prioridad y asociatividad , cuya presencia se debe a la posibilidad de registro de operadores en "cadena" (cómo entender a+b*c : cómo (a+b)*co cómo a+(b*c)?Expresión a-b+c - esto (a-b)+co a-(b+c)?).

Las operaciones integradas en el lenguaje siempre tienen precedencia y asociatividad tradicionales predefinidas. Surge la pregunta: ¿qué prioridades y asociatividad tendrán las versiones redefinidas de estas operaciones, o más aún, las nuevas operaciones creadas por el programador? Hay otras sutilezas que pueden requerir aclaración. Por ejemplo, en C hay dos formas de los operadores de incremento y decremento ++y -- , prefijo y posfijo, que se comportan de manera diferente. ¿Cómo deben comportarse las versiones sobrecargadas de dichos operadores?

Diferentes idiomas tratan estos temas de diferentes maneras. Por lo tanto, en C++, la precedencia y la asociatividad de las versiones sobrecargadas de los operadores se conservan igual que las de los predefinidos en el lenguaje, y las descripciones sobrecargadas de las formas de prefijo y posfijo de los operadores de incremento y decremento usan diferentes firmas:

forma de prefijo Forma de sufijo
Función T&operador ++(T&) Operador T ++(T &, int)
función miembro T&T::operador ++() TT::operador ++(int)

De hecho, la operación no tiene un parámetro entero, es ficticio y se agrega solo para marcar la diferencia en las firmas.

Una pregunta más: ¿es posible permitir la sobrecarga de operadores para tipos de datos integrados y ya declarados? ¿Puede un programador cambiar la implementación de la operación de suma para el tipo de entero integrado? ¿O para el tipo de biblioteca "matriz"? Por regla general, la primera pregunta se responde negativamente. Cambiar el comportamiento de las operaciones estándar para tipos incorporados es una acción extremadamente específica, cuya necesidad real puede surgir solo en casos excepcionales, mientras que las consecuencias dañinas del uso incontrolado de dicha función son difíciles incluso de predecir por completo. Por lo tanto, el lenguaje generalmente prohíbe la redefinición de operaciones para tipos incorporados o implementa un mecanismo de sobrecarga de operadores de tal manera que las operaciones estándar simplemente no pueden anularse con su ayuda. En cuanto a la segunda pregunta (operadores de redefinición ya descritos para tipos existentes), el mecanismo de herencia de clases y anulación de métodos proporciona la funcionalidad necesaria: si desea cambiar el comportamiento de una clase existente, debe heredarla y redefinirla. los operadores descritos en él. En este caso, la clase anterior permanecerá sin cambios, la nueva recibirá la funcionalidad necesaria y no se producirán colisiones.

Anuncio de nuevas operaciones

La situación con el anuncio de nuevas operaciones es aún más complicada. Incluir la posibilidad de tal declaración en el idioma no es difícil, pero su implementación está plagada de dificultades significativas. Declarar una nueva operación es, de hecho, crear una nueva palabra clave de lenguaje de programación, complicada por el hecho de que las operaciones en el texto, como regla, pueden seguir sin separadores con otros tokens. Cuando aparecen, surgen dificultades adicionales en la organización del analizador léxico. Por ejemplo, si el idioma ya tiene las operaciones “+” y el unario “-” (cambio de signo), entonces la expresión a+-bse puede interpretar con precisión como a + (-b), pero si se declara una nueva operación en el programa +-, surge inmediatamente la ambigüedad, porque el misma expresión ya se puede analizar y cómo a (+-) b. El desarrollador e implementador del lenguaje debe lidiar con estos problemas de alguna manera. Las opciones, de nuevo, pueden ser diferentes: exigir que todas las operaciones nuevas sean de un solo carácter, postular que en caso de discrepancias, se elige la versión “más larga” de la operación (es decir, hasta el siguiente conjunto de caracteres leído por el traductor coincide con cualquier operación, se sigue leyendo), intenta detectar colisiones durante la traducción y generar errores en casos controvertidos... De una forma u otra, los lenguajes que permiten la declaración de nuevas operaciones solucionan estos problemas.

No hay que olvidar que para las nuevas operaciones también está el tema de determinar la asociatividad y la prioridad. Ya no existe una solución prefabricada en forma de una operación de lenguaje estándar y, por lo general, solo tiene que configurar estos parámetros con las reglas del idioma. Por ejemplo, haga que todas las operaciones nuevas sean asociativas a la izquierda y déles la misma prioridad fija, o introduzca en el lenguaje los medios para especificar ambas.

Sobrecarga y variables polimórficas

Cuando se usan operadores, funciones y procedimientos sobrecargados en lenguajes fuertemente tipados, donde cada variable tiene un tipo predeclarado, depende del compilador decidir qué versión del operador sobrecargado usar en cada caso particular, sin importar cuán complejo sea. . Esto significa que para los lenguajes compilados, el uso de la sobrecarga de operadores no reduce el rendimiento de ninguna manera; en cualquier caso, hay una llamada de función o operación bien definida en el código objeto del programa. La situación es diferente cuando es posible usar variables polimórficas en el lenguaje, variables que pueden contener valores de diferentes tipos en diferentes momentos.

Dado que el tipo de valor al que se aplicará la operación sobrecargada se desconoce en el momento de la traducción del código, el compilador no tiene la oportunidad de elegir la opción deseada por adelantado. En esta situación, se ve obligado a incrustar un fragmento en el código objeto que, inmediatamente antes de realizar esta operación, determinará los tipos de los valores en los argumentos y seleccionará dinámicamente una variante correspondiente a este conjunto de tipos. Además, tal definición debe hacerse cada vez que se realiza la operación, porque incluso el mismo código, al ser llamado por segunda vez, bien puede ejecutarse de manera diferente ...

Por lo tanto, el uso de la sobrecarga de operadores en combinación con variables polimórficas hace que sea inevitable determinar dinámicamente a qué código llamar.

Crítica

El uso de la sobrecarga no es considerado una bendición por todos los expertos. Si la sobrecarga de funciones y procedimientos, en general, no encuentra serias objeciones (en parte porque no conduce a algunos de los problemas típicos de "operadores", en parte porque es menos tentador hacer un mal uso de ellos), entonces la sobrecarga de operadores, como en principio, y en particular implementaciones del lenguaje, está sujeto a críticas bastante severas por parte de muchos teóricos y profesionales de la programación.

Los críticos señalan que los problemas de identificación, precedencia y asociatividad descritos anteriormente a menudo hacen que lidiar con operadores sobrecargados sea innecesariamente difícil o poco natural:

  • Identificación. Si el lenguaje tiene reglas de identificación estrictas, entonces el programador se ve obligado a recordar para qué combinaciones de tipos hay operaciones sobrecargadas y convertirles manualmente los operandos. Si el lenguaje permite una identificación "aproximada", nunca se puede estar seguro de que en alguna situación bastante complicada, se realizará exactamente la variante de la operación que el programador tenía en mente.
    • La "sobrecarga" de una operación para un tipo particular se determina fácilmente si el lenguaje admite herencia o interfaces ( clases de tipo ). Si el idioma no permite esto, entonces es un problema de diseño. Entonces, en los lenguajes OOP ( Java , C# ) los operadores de métodos se heredan de Object, y no de las clases correspondientes (comparación, operaciones numéricas, bit a bit, etc.) o interfaces predefinidas.
    • La "identificación aproximada" existe solo en idiomas con un sistema de tipos sueltos, donde "la capacidad de pegarse un tiro en el pie " "en una situación bastante difícil" está presente de forma permanente y sin sobrecarga del operador.
  • Prioridad y asociatividad. Si están rígidamente definidos, esto puede ser inconveniente e inapropiado para el área temática (por ejemplo, para operaciones con conjuntos, las prioridades difieren de las aritméticas). Si el programador puede configurarlos, esto se convierte en un generador de errores adicional (aunque solo sea porque las diferentes variantes de una operación resultan tener diferentes prioridades, o incluso asociatividad).
    • Este problema se resuelve en parte definiendo nuevos operadores (por ejemplo, \/tanto /\para disyunción como para conjunción ).

Cuánto puede compensar la conveniencia de utilizar sus propias operaciones el inconveniente de deteriorar la capacidad de control del programa es una pregunta que no tiene una respuesta clara.

Algunos críticos se pronuncian en contra de las operaciones de sobrecarga, basándose en los principios generales de la teoría del desarrollo de software y la práctica industrial real.

  • Los partidarios del enfoque "puritano" de la construcción de lenguajes, como Wirth o Hoare , se oponen a la sobrecarga de operadores simplemente porque supuestamente es fácil prescindir de ella. En su opinión, tales herramientas solo complican el idioma y el traductor, sin proporcionar características adicionales correspondientes a esta complicación. En su opinión, la idea misma de crear una extensión del lenguaje orientada a tareas solo parece atractiva. En realidad, el uso de herramientas de extensión de lenguaje hace que el programa sea comprensible solo para su autor, el que desarrolló esta extensión. El programa se vuelve mucho más difícil de entender y analizar para otros programadores, lo que dificulta el mantenimiento, la modificación y el desarrollo del equipo.
  • Se observa que la posibilidad misma de usar la sobrecarga a menudo juega un papel provocador: los programadores comienzan a usarla siempre que sea posible, como resultado, una herramienta diseñada para simplificar y agilizar el programa se convierte en la causa de su excesiva complicación y confusión.
  • Es posible que los operadores sobrecargados no hagan exactamente lo que se espera de ellos, según su tipo. Por ejemplo, a + bpor lo general (pero no siempre) significa lo mismo b + apero «один» + «два»difiere de «два» + «один»en idiomas donde el operador +está sobrecargado para la concatenación de cadenas .
  • La sobrecarga de operadores hace que los fragmentos de programa sean más sensibles al contexto. Sin conocer los tipos de operandos involucrados en una expresión, es imposible entender qué hace la expresión si usa operadores sobrecargados. Por ejemplo, en un programa C++ , un operador <<puede significar tanto un desplazamiento bit a bit, una salida a un flujo, como un desplazamiento de caracteres en una cadena por un número determinado de posiciones. La expresión a << 1devuelve:
    • el resultado de cambiar bit a bit el valor aun bit a la izquierda si aes un número entero;
    • si a - una cadena, entonces el resultado será una cadena con un carácter de espacio agregado al final (se hará un cambio de carácter por carácter 1 posición a la izquierda), y en diferentes sistemas informáticos el código del carácter de espacio puede diferenciarse;
    • pero si es un flujoa de salida , la misma expresión generará el número 1 en ese flujo .«1»

Este problema se deriva naturalmente de los dos anteriores. Se nivela fácilmente por la aceptación de acuerdos y la cultura general de programación.

Clasificación

La siguiente es una clasificación de algunos lenguajes de programación según si permiten la sobrecarga de operadores y si los operadores están limitados a un conjunto predefinido:

muchos
operadores

Sin sobrecarga

hay una sobrecarga
Solo
predefinido

C
Java
JavaScript
Objective-C
Pascal
PHP
ActionScript
Ir

Ada
C++
C#
D
Objeto Pascal
Perl
Python
Ruby
VB.NET
Delphi
Kotlin
Rust
Swift

maravilloso

Es posible
introducir nuevos

ML
Pico
Lisp

Algol 68
Fortran
Haskell
PostgreSQL
Prólogo
Perl 6
Seed7
Smalltalk
Julia

Notas

  1. Herbert Schildt. La guía completa de C# 4.0, 2011.

Véase también