4.9.18b Sobrecarga de operadores binarios
§1 Sinopsis
Los operadores binarios pueden ser sobrecargados de dos formas:
a. Declarando una función miembro no estática que acepte un argumento
b. Declarando una función no miembro (generalmente friend) que acepte dos argumentos.
Según lo anterior, y dependiendo de la declaración, si @ representa el operador binario, la expresión x @ y entre miembros de una clase C puede ser interpretada como cualquiera de las dos formas:
a. x.operator@(y)
b. operator@(x, y)
Si han sido declaradas ambas formas, se aplica la congruencia estándar de argumentos
( 4.4.1a) para
resolver cualquier posible ambigüedad.
Nota: recuerde la versión a parece recibir solo un argumento, pero en realidad recibe dos si se
considera el argumento implícito this (
4.11.6), de forma que podría considerarse x.operator@(C* this, y). Siendo: C* this = &x
[2].
§2 Sobrecarga del operador suma ( + )
En el ejemplo que sigue utilizamos la clase Vector, ya utilizada en ejemplos anteriores
( 4.9.18a) y sobrecargamos el
operador suma binaria (+), de forma que pueda ser utilizada con tipos
de dicha clase. La técnica es la anteriormente utilizada para el operador de asignación:
#include <iostream>
using namespace std;
class Vector {
public:
float x, y;
Vector operator+ (Vector v) { // función-operador operator+
x = x + v.x;
y = y + v.y;
return *this;
}
};
int main () { // =========
float x = 5, y = 6;
cout << "R = " << x + y << endl; // M.2: versión global
Vector v1 = {1, 2}, v2 = {3, 4};
Vector v3 = v1 + v2;
// M.4: versión sobrecargada
cout << "Rx = " << v3.x << endl;
cout << "Ry = " << v3.y << endl;
}
Salida:
R = 11
Rx = 4
Ry = 6
Comentario
El ejemplo compila sin dificultad, confirmando que el operador + puede ser utilizado en M.4 con los objetos
v1 y v2 de la clase Vector. El resultado obtenido para v3 es el esperado. Sin embargo, a pesar de su
aparente idoneidad, tampoco en este caso el operador ha sido sobrecargado correctamente
( 4.9.18a). Si sustituimos
las referencias explícitas a v3 (en las sentencias M.4 y siguientes) por dos resultados auxiliares en la sentencia de salida:
cout << "Rx = " << (v1 + v2).x << endl; // M.4:
cout << "Ry = " << (v1 + v2).y << endl; // M.5:
El programa suministra la desconcertante salida siguiente:
R = 11
Rx = 4
Ry = 10
La razón de que el valor obtenido para Ry no sea el esperado, estriba en que con el diseño actual, la función
operator+ modifica el primer operando, exactamente como lo hacía la función operator= [1]. Esta
asignación oculta también podría ser manifestada añadiendo una línea a la versión inicial del programa:
cout << "v1.x = " << v1.x << " v1.y = " << v1.y << endl; // M.7:
En este caso las salidas habrían sido:
R = 11
Rx = 4
Ry = 6
v1.x = 4 v1.y = 6
§2.1 Para evitar este efecto lateral indeseado de operator+, modificamos su diseño, de forma que no
altere ninguno de los operandos involucrados. Para ello utilizamos un objeto nuevo al que aplicamos el resultado:
#include <iostream>
using namespace std;
class Vector {
public:
float x, y;
Vector operator+ (const Vector& v) { // función-operador operator+
Vector vr; // objeto temporal
vr.x = x + v.x;
vr.y = y + v.y;
return vr;
};
};
int main () { // ===============
float x = 5, y = 6;
cout << "R = " << x + y << endl;
Vector v1 = {1, 2}, v2 = {3, 4};
Vector v3 = v1 + v2;
cout << "Rx = " << v3.x << endl;
cout << "Ry = " << v3.y << endl;
cout << "v1.x = " << v1.x << " v1.y = " << v1.y << endl;
}
Salida:
R = 11
Rx = 4
Ry = 6
v1.x = 1 v1.y = 2
Comentario
La última salida nos confirma que el diseño utilizado es correcto. Proporciona los resultados apetecidos además de mantener inalterados los valores del primer operando.
Observe que la función operator+ ha sido modificada de forma que, además de incluir el objeto temporal vr, el
argumento ha sido declarado const y pasado por referencia. El resultado es que, además de proporcionar una operatoria
correcta, en términos de velocidad de ejecución y memoria requerida, esta solución es mucho más eficaz que la anterior
( 4.2.3).
§2.2 Ejemplo
Finalmente presentaremos una versión análoga al ejemplo aneterior pero utilizando una función-operador (friend) externa a la clase para sobrecargar el operador suma + con miembros de la clase Vector:
#include <iostream>
using namespace std;
class Vector {
public:
float x, y;
friend Vector operator+ (const Vector&, const Vector&);
};
Vector operator+ (const Vector& v1, const Vector& v2) { // función operator+
Vector vr; // objeto temporal
vr.x = v1.x + v2.x;
vr.y = v1.y + v2.y;
return vr;
};
int main () { // ===============
float x = 5, y = 6;
cout << "R = " << x + y << endl;
Vector v1 = {1, 2}, v2 = {3, 4};
Vector v3 = v1 + v2;
cout << "Rx = " << v3.x << endl;
cout << "Ry = " << v3.y << endl;
cout << "v1.x = " << v1.x << " v1.y = " << v1.y << endl;
}
Por supuesto la salida es exactamente igual que en el caso anterior.
[1] En el próximo capítulo veremos que esta circunstancia "indeseada" (en este caso), es
precisamente aprovechada cuando se sobrecargan los operadores unarios incremento y decremento
( 4.9.18c).
[2] Naturalmente estas sentencias tienen una finalidad meramente didácticas y no son sintácticamente correctas.