SOBRECARGA DE OPERADORES

SOBRECARGA DE OPERADORES

Un aspecto importante del C++, unido a la sobrecarga de funciones es la sobrecarga de operadores. A la mayor parte de los operadores del C++ (con poquísimas excepciones) se les puede asignar particularidades específicas de una clase concreta. Por ejemplo una clase que define una lista encadenada (linked list) podría hacer uso de un operador + de modo completamente diferente a como se concibe en las operaciones aritméticas. Un operador de este tipo podría servir en una clase "lista_cadena" para añadir un nuevo elemento a la lista sin que con ello se vieran alteradas sus propiedades como operador de suma con variables de tipo numérico.
Para sobrecargar un operador es necesario definir el significado de la operación respecto a la clase a la cual se aplica. En este caso se crea un función operator que define la acción. La sintaxis es la siguiente:

En este caso se entiende por tipo el tipo de valor de la operación y puede ser de un tipo de dato cualquiera. La mayoría de las veces el tipo es la clase en la cual tiene lugar la sobrecarga. Las funciones operadores tienen que ser elementos de la clase o llevar el calificativo friend (más tarde se verá su significado).
Como ejemplo simple véase la clase tre_dim que contiene las coordenadas espaciales de un objeto. El programa sobrecarga los operadores + y =.

 

#include <iostream.h>
class tre_dim

{

  • int x, y, z; // coordenadas 3-d
    public:
    tre_dim operator+(tre_dim t);
    tre_dim operator=(tre_dim t);
    void visualizza(void) ;
    void assegna(int mx, int my, int mz);

};

// Sobrecarga del operador +.
tre_dim tre_dim::operator+(tre_dim t)


  • {
    tre_dim temp;
    temp.x = x+t.x;
    temp.y = y+t.y;
    temp.z = z+t.z;
    return temp;
    }

 

// Sobrecarga del operador =.
tre_dim tre_dim::operator=(tre_dim t)


  • {
    x = t.x;
    y = t.y;
    z = t.z;
    return *this;
    }

 

// visualiza las coordenadas X, Y, Z
void tre_dim::visualizza(void)


  • {
    cout << x << ", ";
    cout << y << ", ";
    cout << z << "\n";
    }

 

// Asignación de las coordenadas
void tre_dim::assegna(int mx, int my, int mz)


  • {
    x = mx; y = my; z = mz;
    }

 

main(void)
{


  • tre_dim a, b, c;
    a.assegna(1, 2, 3);
    b.assegna(10, 10, 10);
    a.visualizza();
    b.visualizza();
    c = a+b; // adición de a , b c.
    visualizza();
    c = a+b+c; // adición de a, b , c
    c.visualizza();
    c = b - a; // demostración de asignación múltiple
    b.visualizza();
    return O;
  • }

 

La visualización es la siguiente:

1, 2, 3
10, 10, 10
11, 12, 13
22, 24, 26
1, 2, 3
1, 2, 3

Puede resultar extraño que en las funciones operador del ejemplo, aún tratándose de operaciones binarias, no haya más que un solo parámetro. Esta aparente contradicción está justificada si se tiene en cuenta el uso del elemento this el cual se explica más adelante. Gracias a dicho elemento implícito en todas las clases para definir un operador solo hay que especificar un argumento. Así en la instrucción:

temp.x = x + t.x;

la x se refiere a this ->, que es la x asociada al objeto que efectúa la llamada a la función operador. En cualquier caso el objeto a la izquierda del operador es el que provoca la llamada del operador. El objeto a la derecha es el parámetro que se pasa a la función. En general cuando se utiliza un elemento función para sobrecargar un operador unario no son necesarios parámetros, mientras que para sobrecargar un operador binario es necesario un solo parámetro. El operador ternario ? no puede ser sobrecargado.
En ambos casos el objeto que llama a la función operador es pasado implícitamente por medio del operador this.
En el ejemplo la sobrecarga del operador + se limita a sumar las coordenadas pertenecientes a los dos objetos sin modificarlas dando como valor de retorno la variable temporal de tipo tre_dim. Para comprender la operación veamos la similitud con el operador aritmético. La suma de 10 + 12 da como resultado 22. En la operación ni el 10 ni el 12 se modifican.
Otro aspecto que se debe destacar es que el operador + da un valor de retorno que es justamente del tipo tre_dim lo cual permite aplicarlo a operaciones más complejas tal como:

d=a+b+c

A diferencia del operador + el operador de asignación (=) sufre un modificación de sus argumentos (después de todo esta es la finalidad de dicho operador). Como se ha dicho la función operator=() es llamada por el objeto a la izquierda del mismo y es este el que debe ser modificado por la operación. La asignación por otra parte debe de dar un valor pues tanto en el C++ como en el C la operación produce el valor que se encuentra a la derecha del signo =. En consecuencia para permitir una instrucción como la siguiente:

a = b = c = d;

es necesario que el operador operator=() retorne el objeto apuntado por this que es el objeto que en la instrucción se encuentra a la izquierda.
También es posible sobrecargar operadores unarios tales como ++ o --.Como ya se ha dicho en operadores unarios no existen argumentos en la función operator. La operación se aplica al objeto que genera la llamada de la función a través del puntero this pasado de modo implícito.
Ampliando el programa precedente, se puede codificar una versión en la que se implemente el operador ++

 

#include <iostream.h>
class tre_dim
{


  • int x, y, z; // coordenadas tridimensionales(3-d)

    public:
    tre_dim operator+(tre_dim op2); // op1 implicito
    tre_dim operator=(tre_dim op2); // op1 implicito
    tre_dim operator++(void); // op1 implicito.
    void visualizza(void) ;
    void assegna(int mx, int my, int mz);

};

tre_dim tre_dim::operator+(tre_dim op2)


  • {
    tre_dim temp;
    temp.x = x+op2.x; // adicion etre enteros
    temp.y = y+op2.y; // el operador + tine el significado
    temp.z = z+op2.z; // original
    return temp;
    }

 

e_dim tre_dim::operator=(tre_dim op2)


  • {
    x = op2.x; // estro son asignaciones entre enteros
    y = op2.y; // el operador = tiene el significado
    z = op2.z; // original
    return *this;
    }

 

// Sobrecarga de un operador unario.
tre_dim tre_dim::operator++(void)


  • {
    x++ ;
    Y++ ;
    z++ ;
    return *this;
    }

 

// Visualiza las coordenadas X, Y, Z
void tre_dim::visualizza(void)


  • {
    cout << x << ", ";
    cout << y << ", ";
    cout << z << "\n";
    }

 

// Asigna las coordenadas
void tre_dim::assegna(int mx, int my, int mz)


  • {
    x = mx;
    y = my; z = mz;
    }


 

main(void)
{


  • tre_dim a, b, c;
    a.assegna(1, 2, 3);
    b.assegna(10, 10, 10);
    a.visualizza();
    b.visualizza().
    c = a+b; // suma de a , b
    c.visualizza();
    c = a+b+c; // suma de a, b , c
    c.visualizza();
    c = b = a; // asingación múltiple
    c.visualizza();
    b.visualizza().
    c++; // incremento de c
    c.visualizza();
    return 0;
  • }

 

Cuando se sobrecarga ++ es importante tener presente que no es posible determinar en el interior de la función operador si el operador precede o sigue al operando. En otras palabras, la función operator no es capaz de reconocer si la expresión que genera la llamada es:

++OBJ ;

o

OBJ++;

donde por OBJ se entiende el objeto sobre el cual se ejecuta la operación. No hay ninguna regla que imponga que el operador sobrecargado tenga que tener similitud con el que preve el C++. Como ejemplo podemos señalar el operador << aplicado al flujo estandard cout perteneciente a la clase ostream el cual realiza una operación de salida o visualización. Otro ejemplo se tiene en el operador >> aplicado el flujo cin de la clase istream el cual realiza una operación de lectura. Ambos operadores tienen un significado bien distinto a los del C++ que realizan una operación de corrimiento de bits a la izquierda (<<) o a la derecha (>>).

Sin embargo con el fin de ganar consistencia y comprensibilidad del código, un operador sobrecargado debe reflejar tanto como sea posible las características del original. Por ejemplo el operador + en relación con la clase tre_dim es análogo conceptualmente al operador + entre enteros. Se tienen pocas ventajas di se define un operador que efectúa una operación totalmente inesperada y en desacuerdo con lo que hace el operador original previsto por el C++.

Existen por otra parte limitaciones en la sobrecarga de operadores. Entre otras son es posible alterar el orden de prioridad de los operadores. Tampoco se puede modificar el número de operandos previstos por el operador aunque si es posible programar el operador de modo que ignore algún operando.
Finalmente cabe decir que a excepción del operador "=" los operadores sobrecargados pueden ser heredados por cualquier clase derivada. El operador "=" debe ser definido explícitamente.
Los únicos operadores que no pueden ser sobrecargados son:

. :: .* ?

LA PALABRA LLAVE this

Para afrontar la sobrecarga de operadores es necesario introducir una de las palabras llave del C++ que a tal efecto representa un elemento esencial.
Cada vez que se llama a un elemento función en una clase se le pasa automáticamente un puntero que apunta objeto que ha llamado dicha función. Para acceder a este puntero se utiliza la variable implícita this.
Para entender el significado de this observemos el siguiente programa:

 

#include <iostream.h>
class cl
}


  • int i;
    public:
    void load_i(int val)

  • {
    this->i = val; // equivalente a i = val
    }

lnt get_i(void)

  • {
    return this->i;// equivalente a return i
    }
 

};

main(void)
{


  • o.load_i(100);
    cout << o.get_i();
    return O;
  • }

 

Este programa visualiza el valor 100.
Conviene remarcar que programar con un estilo como el mostrado en el ejemplo anterior no tiene ningún sentido. Dicho ejemplo tiene solo el fin didáctico de facilitar la comprensión del puntero this.

FUNCIONES OPERADOR FRIEND

Una función operador puede ser friend de una clase. Como ya se ha evidenciado, las funciones friend no preven el argumento implícito this. En consecuencia cuando se recurre a una función de este tipo para sobrecargar un operador, se pasan explícitamente dos operandos en caso de operadores binarios y un operando en caso de operadores unarios.
Los operadores que no pueden utilizar funciones friend son "=", "()", "[]" y "->".
Todos los demás pueden utilizar indiferentemente elementos función o funciones friend para realizar la operación especificada por la propia clase.
A continuación se expone una versión modificada del programa precedente en el cual una función friend se utiliza en lugar de un elemento función para sobrecargar el operador +.

 

#include <iostream.h>
class tre_dim
{


  • int x, y, z; // coordinadas tridimensionales (3-d)
    public:
    friend tre_dim operator+(tre_dim op1,tre_dim op2);
    tre_dim operator=(tre_dim op2); // op1 es implicito
    tre_dim operator++(void); //tambien aqui op1 es implicito.
    void visualizza(void) ;
    void assegna(int mx, int my, int mz);

};

// Esta función es ahora del tipo friend
tre_dim tre_dim::operator+(tre_dim op1,tre_dim op2)


  • {
    tre_dim temp;
    temp.x = x+op2.x; // suma de enteros
    temp.y = y+op2.y;
    temp.z = z+op2.z;
    return temp;
    }

 

e_dim tre_dim::operator=(tre_dim op2)


  • {
    x = op2.x; // asignación de enteros
    y = op2.y;
    z = op2.z;
    return *this;
    }

 

// Sobrecarga de un operador unario.
tre_dim tre_dim::operator++(void)


  • {
    x++ ;
    Y++ ;
    z++ ;
    return *this;
    }


 

// visualiza las coordenadas X, Y, Z
void tre_dim::visualizza(void)


  • {
    cout << x << ", ";
    cout << y << ", ";
    cout << z << "\n";
    }

 

// Asigna las coordenadas
void tre_dim::assegna(int mx, int my, int mz)


  • {
    x = mx;
    y = my; z = mz;
    }

 

main(void)
{


  • tre_dim a, b, c;
    a.assegna(1, 2, 3);
    b.assegna(10, 10, 10);
    a.visualizza();
    b.visualizza().
    c = a+b; // suma de a , b
    c.visualizza();
    c = a+b+c; // suma de a, b , c
    c.visualizza();
    c = b = a; // Asignación múltiple
    c.visualizza();
    b.visualizza().
    c++; // incremento de c
    c.visualizza();
    return 0;
  • }

 

Como se puede notar, a la función operator+() se le pasa dos operandos. El de la izquierda lo hace a través de op1 y el de la derecha a través de op2.
Hay numerosas ocasiones en las que sobrescribir un operador mediante una función friend es indispensable pues como es sabido con this se pasa un puntero que apunta al objeto que llama al elemento función operador. En caso de operadores binarios es objeto a la izquierda el que llama a la función lo cual impide invertir el orden de los operandos. Por ejemplo la instrucción que sigue es perfectamente válida:

ob = ob + 10; // funcionea correctamente.

El objeto ob se encuentra a la izquierda del operador + y llama a su función operador que seguramente es capaz de sumar un entero a un elemento de ob.
En cambio la llamada que sigue no es correcta:

ob = 10 + ob; // instrucción errónea.

El hecho de que el objeto a la izquierda del operador sea un entero hace que la función operador no esté definida.
El problema se puede solucionar si se sobrescribe el operador + mediante dos funciones friend en cuyo caso a la función operator+() se le pasan dos argumentos cuyos tipos los podemos elegir. El programa que sigue ilustra el método

 

#include <iostream.h>
class CL
{


  • public:
    int contatore;
    CL operator=(int i);
    friend CL operator+(CL ob, int i);
    friend CL operator+(int i, CL ob);

};

CL CL::operator=(int i)


  • {
    contatore = i;
    return *this;
    }

 

// Aqui se define ob + entero.
CL operator+(CL ob, int i)


  • {
    CL temp;
    temp.contatore = ob.contatore + i;
    return temp;
    }

 

// Aqui se define entero + ob.
CL operator+(int i, CL ob)


  • {
    CL temp;
    temp.contatore = ob.contatore + i;
    return temp;
    }

 

main(void) CL obj;
{


  • obj = 10; cout << obj.contatore << " n; // visualiza 10
    obj = 10 + obj; // suma de objeto + entero
    cout << obj.contatore << " "; // visualiza 20
    obj = obj + 12; // suma de entero + objeto
    cout << obj.contatore; // visualiza 32
    return 0;
  • }

 

La función operator+() se sobrecarga dos veces con el fin de que pueda distinguir el orden de los operandos.