Sobrecarga de operadores unitarios

Ahora le toca el turno a los operadores unitarios, que son aquellos que sólo requieren un operando, como la asignación o el incremento.

Cuando se sobrecargan operadores unitarios en una clase el operando es el propio objeto de la clase donde se define el operador. Por lo tanto los operadores unitarios dentro de las clases no requieren parámetros

Sintaxis:

<tipo> operator<operador unitario>();

Normalmente el <tipo> es la clase para la que estamos sobrecargando el operador. Sigamos con el ejemplo de la clase para el tratamiento de tiempos, sobrecargaremos ahora el operador de incremento ++:

class Tiempo {
...
Tiempo operator++();
...
};
 
Tiempo Tiempo::operator++() {
   minuto++;
   while(minuto >= 60) {
      minuto -= 60;
      hora++;
   }
   return *this;
}
...
 
T1.Mostrar();
++T1;
T1.Mostrar();
...

Operadores unitarios sufijos

Lo que hemos visto vale para el preincremento, pero, ¿cómo se sobrecarga el operador de postincremento?

En realidad no hay forma de decirle al compilador cuál de las dos modalidades del operador estamos sobrecargando, así que los compiladores usan una regla: si se declara un parámetro para un operador ++ ó -- se sobrecargará la forma sufija del operador. El parámetro se ignorará, así que bastará con indicar el tipo.

También tenemos que tener en cuenta el peculiar funcionamiento de los operadores sufijos, cuando los sobrecarguemos, al menos si queremos mantener el comportamiento que tienen normalmente.

Cuando se usa un operador en la forma sufijo dentro de una expresión, primero se usa el valor actual del objeto, y una vez evaluada la expresión, se aplica el operador. Si nosotros queremos que nuestro operador actúe igual deberemos usar un objeto temporal, y asignarle el valor actual del objeto. Seguidamente aplicamos el operador al objeto actual y finalmente retornamos el objeto temporal.

Veamos un ejemplo:

class Tiempo {
...
Tiempo operator++();    // Forma prefija
Tiempo operator++(int); // Forma sufija
...
};
 
Tiempo Tiempo::operator++() {
   minuto++;
   while(minuto >= 60) {
      minuto -= 60;
      hora++;
   }
   return *this;
}
 
Tiempo Tiempo::operator++(int) {
   Tiempo temp(*this); // Constructor copia
 
   minuto++;
   while(minuto >= 60) {
      minuto -= 60;
      hora++;
   }
   return temp;
}
...
 
// Prueba:
T1.Mostrar();
(T1++).Mostrar();
T1.Mostrar();
(++T1).Mostrar();
T1.Mostrar();
...

Salida:

17:9 (Valor inicial)
17:9 (Operador sufijo, el valor no cambia 
      hasta después de mostrar el valor)
17:10 (Resultado de aplicar el operador)
17:11 (Operador prefijo, el valor cambia 
       antes de mostrar el valor)
17:11 (Resultado de aplicar el operador)

Operadores unitarios que pueden sobrecargarse

Además del operador ++ y -- pueden sobrecargarse prácticamente todos los operadores unitarios:

+, -, ++, --, *, & y !.

Operadores de conversión de tipo

Volvamos a nuestra clase Tiempo. Imaginemos que queremos hacer una operación como la siguiente:

Tiempo T1(12,23);
unsigned int minutos = 432;
 
T1 += minutos;

Con toda probabilidad no obtendremos el valor deseado.

Como ya hemos visto, en C++ se realizan conversiones implícitas entre los tipos básicos antes de operar con ellos, por ejemplo para sumar un int y un float, se convierte el entero a float. Esto se hace también en nuestro caso, pero no como esperamos.

El valor "minutos" se convierte a un objeto Tiempo, usando el constructor que hemos diseñado. Como sólo hay un parámetro, el parámetro m toma el valor 0, y para el parámetro h se convierte el valor "minutos" de unsigned int a int.

El resultado es que se suman 432 horas, cuando nosotros queremos sumar 432 minutos.

Esto se soluciona creando un nuevo constructor que tome como parámetro un unsigned int.

Tiempo(unsigned int m) : hora(0), minuto(m) {
   while(minuto >= 60) {
      minuto -= 60;
      hora++;
   }
}

Ahora el resultado será el adecuado.

En general podremos hacer conversiones de tipo desde cualquier objeto a un objeto de nuestra clase sobrecargando el constructor.

Pero también se puede presentar el caso contrario. Ahora queremos asignar a un entero un objeto Tiempo:

Tiempo T1(12,23);
int minutos;
 
minutos = T1;

En este caso obtendremos un error de compilación, ya que el compilador no sabe convertir un objeto Tiempo a entero.

Para eso tenemos que diseñar nuestro operador de conversión de tipo, que se aplicará automáticamente.

Los operadores de conversión de tipos tienen el siguiente formato:

operator <tipo>();

No necesitan que se especifique el tipo del valor de retorno, ya que este es precisamente <tipo>. Además, al ser operadores unitarios, tampoco requieren argumentos, puesto que se aplican al propio objeto.

class Tiempo {
...
operator int(); 
...
 
operator int() {
   return hora*60+minuto;
}

Por supuesto, el tipo no tiene por qué ser un tipo básico, puede tratarse de una estructura o una clase.