Sobrecarga de operadores

Sobrecarga de operadores
Información sobre la plantilla
Concepto:Se llama sobrecarga de operadores cuando reutilizando el mismo operador con un número de usos diferentes, y el compilador decide como usar ese operador dependiendo sobre qué opera.

Sobrecarga de operadores. Los operadores son un tipo de tokens que indican al compilador la realización de determinadas operaciones sobre variables u otros. La sobrecarga de operadores permite redefinir ciertos operadores, como '+' y '-', para usarlos con las clases que hemos definido. Se llama sobrecarga de operadores cuando reutilizando el mismo operador con un número de usos diferentes, y el compilador decide como usar ese operador dependiendo sobre qué opera.

La sobrecarga de operadores solo se puede utilizar con clases, no se pueden redefinir los operadores para los tipos simples predefinidos.

Los operadores lógicos && y || pueden ser sobrecargados para las clases definidas por el programador, pero no funcionaran como operadores de short circuit. Todos los miembros de la construcción lógica serán evaluados sin ningún problema en lo que se refiere a la salida. Naturalmente los operadores lógicos predefinidos continuaran siendo operadores de short circuit como era de esperar, pero no los sobrecargados.

Los operadores aceptan uno o varios operandos de tipo específico (alguno de los tipos básicos preconstruidos en el lenguaje), produciendo y/o modificando un valor de acuerdo con ciertas reglas. Sin embargo, C++ permite redefinir la mayor parte de ellos. Es decir, permite que puedan aceptar otro tipo de operandos (distintos de los tipos básicos) y seguir otro comportamiento, al tiempo que conservan el sentido y comportamiento originales cuando se usan con los operandos normales. Esta posibilidad recibe el nombre de sobrecarga del operador.

Permanencia de las leyes formales

El lenguaje C++ no impone ninguna restricción en la forma que adopte la sobrecarga de un operador. De forma que se puede conseguir que incluso operadores básicos como la suma (+), la asignación (=) o la identidad (==) adquieran para los tipos abstractos un carácter totalmente distinto del que adoptan para los tipos básicos. No obstante, lo normal, lógico, y aconsejable, es mantener la máxima homogeneidad conceptual en el polimorfismo. Es decir, que el efecto básico de los operadores (la imagen mental de su significado) se mantenga, aunque su implementación concreta varíe de una clase a otra. Por ejemplo, las definiciones de suma (+), asignación (=) e identidad (==) para los elementos de una clase C deberían garantizar que después de ejecutada la sentencia c = d; sobre instancias c y d de dicha clase, el resultado de (c == d) fuese true. El resultado de aplicar el constructor-copia para crear un objeto a partir de otro debería producir un nuevo objeto igual que el modelo.

Otro aspecto que debería mantenerse, es que los operadores que normalmente no tienen efectos laterales, se mantengan igualmente libres de tales efectos en sus versiones sobrecargadas. Lo anterior puede expresarse con otras palabras: debe procurarse que las propiedades formales de los operadores matemáticos se mantengan también en la versión sobrecargada. Por ejemplo, que la suma sea conmutativa y asociativa, que la identidad sea simétrica y transitiva. Al tratar la sobrecarga de los operadores relacionales se abunda en estos conceptos.

Sinopsis

A excepción de los que se detallan, el lenguaje C++ permite la sobrecarga los operadores estándar. La nueva versión del operador se diseña de forma que presente un comportamiento especial cuando los operandos sean instancias de clase. Por ejemplo, el operador de identidad = = podría ser definido en una hipotética clase Complejo para verificar la identidad de dos números complejos, al mismo tiempo que mantendría su uso normal cuando se utilizara con tipos básicos (int, float, char,).

Para distinguir unas de otras, a las versiones de los operadores preconstruidas en el lenguaje las denominamos versiones globales, mientras que a las definidas por el usuario, versiones sobrecargadas.

Operadores sobrecargables

El lenguaje C++ permite redefinir la funcionalidad de los siguientes operadores:

  +     -     *     /     %     ^     &
  |     ~     !     =     <     >     +=
  -=    *=    /=    %=    ^=    &=    |=
  <<    >>    >>=   <<=   ==    !=    <=
  >=    &&    ||    ++    --    ->*    ,
  ->    []    ()    new   new[]  delete delete[]

Los operadores +, -, * y & son sobrecargables en sus dos versiones, unaria y binaria. Es decir: suma binaria +; más unitario +; multiplicación *; indirección *; referencia & y manejo de bits &.

Es notable que C++ ofrece casos de operadores sobrecargados incluso en su Librería Estándar. Por ejemplo, los operadores == y != para la clase type_info. Sin embargo, la posibilidad de sobrecarga no se extiende a todos los operadores (ver las excepciones).

Limitaciones

La sobrecarga de un operador no puede cambiar el número de operandos o la asociatividad y precedencia del mismo. En otras palabras: se puede modificar su funcionalidad pero no su gramática original. Por ejemplo, un operador unario no puede ser transformado en binario y viceversa.

  • Los operadores globales no pueden ser sobrecargados.
  • No pueden definirse nuevos tokens como operadores. En caso necesario deben utilizarse funciones. Por ejemplo, no puede definirse ** como un token para representar la exponenciación (a ** b); en todo caso utilizar algo así: pow(a, b).
  • No es posible redefinir el sentido de un operador aplicado a un puntero. Por ejemplo, no es posible modificar el sentido del operador suma (+) entre un puntero y un entero (sentencia L.3).
  class CL { /* ... */ };
  CL c1, *cpt1 = &c1;
  CL* cpt2 = cpt1 + 5;        // L.3

En otras palabras: no es posible modificar la aritmética de punteros sobrecargando sus operadores.

Excepciones

En la lista de los Operadores sobrecargables, puede verificarse que todos los operadores pueden ser sobrecargados, incluyendo new, new[ ], delete y delete[ ], excepto los siguientes:

  • Selector directo de componente .
  • Operador de indirección de puntero-a-miembro .*
  • Operador de acceso a ámbito  ::
  • Condicional ternario  ?:
  • Directivas de preprocesado # y # #
  • sizeof, typeid

Los operadores asignación =; elemento de matriz [ ] ; invocación de función ( ) y selector indirecto de miembro -> pueden ser sobrecargados solamente como funciones-miembro no estáticas y no pueden ser sobrecargados para las enumeraciones. Cualquier intento de sobrecargar la versión global de estos operadores produce un error de compilación.

Con la excepción de los anteriores ( =, [ ], ( ) y ->), los operadores también pueden ser sobrecargados para las enumeraciones y pueden utilizarse funciones-miembro estáticas.

La función-operador

Los operadores C++ pueden considerarse funciones con identificadores un tanto especiales. Por ejemplo, cuando tenemos el operador de subíndice de matriz, x[y], donde x es un objeto de la clase X, el compilador lo traduce a la expresión: x.operator[](y). Es decir, lo interpreta como la invocación de un método de nombre operator[ ].

Este comportamiento del compilador puede hacerse extensivo al resto de operadores, de forma que, cuando se trata de miembros de clases, si @ representa un operador binario, la expresión a @ b es en realidad una forma abreviada de representar la invocación de una función-miembro (método): a.operator@(b), o de una función externa equivalente: operator@(a, b).

Igualmente, si @ representa un operador unario (por ejemplo, el operador preincremento ++, la expresión @ a es la forma abreviada de representar la invocación de una función-miembro que no acepte argumentos: a.operator@( ), o de una función externa equivalente que acepte un argumento: operator@(a).

Estas funciones, denominadas función-operador, determinan el tipo de los operandos; el Lvalue y orden de evaluación que se aplicará cuando se utilice el operador. Como consecuencia, la sobrecarga de un operador se realiza bajo la forma de sobrecarga de la función-operador y su definición determinará el nuevo comportamiento. Como en el caso general de sobrecarga de funciones, el compilador distinguirá las diferentes funciones-operador por el contexto de la llamada (número y tipo de los argumentos).

La palabra clave operator seguida del símbolo del operador conforma el identificador de la función-operador. Ejemplos:

  <tipo-devuelto> operator +  (/*...*/) {/*...*/} ;
  <tipo-devuelto> operator [] (/*...*/) {/*...*/} ;
  <tipo-devuelto> operator -  (/*...*/) {/*...*/} ;
  <tipo-devuelto> operator ->* (/*...*/) {/*...*/} ;
  <tipo-devuelto> operator =  (/*...*/) {/*...*/} ;
  <tipo-devuelto> operator= (/*...*/) {/*...*/} ;

Es indiferente dejar un espacio entre la palabra operator y el símbolo del operador (las dos últimas líneas son equivalentes). Además la identificación operador ↔ función-operador no es solo interna, también puede ser utilizada explícitamente en el código. Una función-operador invocada con los argumentos apropiados, se comporta en cualquier sentencia como un operador con sus operandos. Por ejemplo:

  UnaClase c1, c2, c3;
  ...
  c2 = c1;              // L.3: Ok.  asignación
  c2.operator=(c1);     // L.4: Ok.  la misma asignación
  c3 = c1 + c2;         // L.5: suma y asignación
  c3.operator=(c1.operator+(c2));  // Ok. equivalente a L.5:

Las sentencias L.3 y L.4 son equivalentes. Aunque legal, la expresión L.4 no es la forma usual de invocar al operador de asignación =.

La función-operador no puede utilizar argumentos por defecto salvo en los casos que se autorizan expresamente. Tampoco pueden tener más o menos argumentos que los indicados en cada caso.

La función-operador puede ser miembro o friend (función externa) de la clase para la que se define. Que se utilice una u otra forma es, a veces, cuestión de preferencia personal, pero en otras viene obligada. En cualquier caso, las funciones-operador son buenas candidatas para ser declaradas funciones inline

  • Se suelen declarar miembros de la clase los operadores unarios (de un solo operando), o los que modifican el primer operando (caso de los operadores de asignación). En estos casos el primer operando debe de ser necesariamente una instancia de esa clase, en concreto el objeto que constituye el argumento implícito. Por esta causa, salvo que sean declaradas funciones estáticas, puesto que el puntero this es incluido de forma implícita en la declaración, sólo hará falta incluir el segundo operando en la lista de argumentos de la función-operador.
  • Se suelen declarar friend los operadores que aceptan varios operandos sin modificarlos (por ejemplo los operadores aritméticos y lógicos). En estos casos se exige que al menos uno de sus operandos (argumentos) sea del tipo de la clase para la que se define (las funciones-operador que redefinen los operadores new y delete son la excepción de esta regla).

Herencia y sobrecarga de operadores

A excepción del operador de asignación simple = todas las funciones-operador sobrecargadas en una clase antecesora son heredadas en las clases derivadas. Si B es base de la clase D, un operador @ sobrecargado para B puede ser sobrecargado más tarde para D. Es decir, pueden coexistir las siguientes definiciones:

  B::operator@( ){ /* definicion para super-Clase */ }
  D::operator@( ){ /* definicion para clase derivada */ }

Puede Consultar

Fuente