La segunda forma de producir conversiones automáticas de tipo es a través de la
sobrecarga de operadores. Puede crear un método que tome el tipo actual
y lo convierta al tipo deseado usando la palabra reservada
operator
seguida del tipo al que quiere convertir. Esta forma de
sobrecarga de operadores es única porque parece que no se especifica un tipo de
retorno -- el tipo de retorno es el nombre del operador que está sobrecargando. He
aquí un ejemplo:
//: C12:OperatorOverloadingConversion.cpp class Three { int i; public: Three(int ii = 0, int = 0) : i(ii) {} }; class Four { int x; public: Four(int xx) : x(xx) {} operator Three() const { return Three(x); } }; void g(Three) {} int main() { Four four(1); g(four); g(1); // Calls Three(1,0) } ///:~
Listado 12.20. C12/OperatorOverloadingConversion.cpp
Con la técnica del constructor, la clase destino realiza la conversión, pero con
los operadores, la realiza la clase origen. El valor de la técnica del constructor
es que puede añadir una nueva ruta de conversión a un sistema existente al crear
una nueva clase. Sin embargo, creando un constructor con un único argumento
siempre define una conversión automática de tipos (incluso si requiere más de un
argumento si el resto de los argumentos tiene un valor por defecto), que puede no
ser lo que desea (en cuyo caso puede desactivarlo usando
explicit
). Además, no hay ninguna forma de usar una conversión por
constructor desde un tipo definido por el usuario a un tipo incorporado; eso sólo
es posible con la sobrecarga de operadores.
Una de las razones más convenientes para usar operadores sobrecargados globales en lugar de operadores miembros es que en la versión global, la conversión automática de tipos puede aplicarse a cualquiera de los operandos, mientras que con objetos miembro, el operando de la parte izquierda debe ser del tipo apropiado. Si quiere que ambos operandos sean convertidos, la versión global puede ahorrar un montón de código. He aquí un pequeño ejemplo:
//: C12:ReflexivityInOverloading.cpp class Number { int i; public: Number(int ii = 0) : i(ii) {} const Number operator+(const Number& n) const { return Number(i + n.i); } friend const Number operator-(const Number&, const Number&); }; const Number operator-(const Number& n1, const Number& n2) { return Number(n1.i - n2.i); } int main() { Number a(47), b(11); a + b; // OK a + 1; // 2nd arg converted to Number //! 1 + a; // Wrong! 1st arg not of type Number a - b; // OK a - 1; // 2nd arg converted to Number 1 - a; // 1st arg converted to Number } ///:~
Listado 12.21. C12/ReflexivityInOverloading.cpp
La clase Number
tiene tanto un miembro
operator+
como un friend operator-
. Dado que hay un
constructor que acepta un argumento int
simple, se puede convertir
un int
automáticamente a Number
, pero sólo
bajo las condiciones adecuadas. En main()
, puede ver que
sumar un Number
a otro Number
funciona bien dado que tiene una correspondencia exacta con el operador
sobrecargado. Además, cuando el compilador ve un Number
seguido de un +
y de un int
, puede hacer la
correspondencia al método Number::operator+
y convertir el
argumento int
an Number
usando el
constructor. Pero cuando ve un int
, un +
y un
Number
, no sabe qué hacer porque todo lo que tiene es
Number::operator+
que requiere que el operando de la izquierda sea
ya un objeto Number
. Así que, el compilador genera un
error.
Con friend operator-
las cosas son diferentes. El compilador
necesita rellenar ambos argumentos como quiera; no está restringido a tener un
Number
como argumento de la parte izquierda. así que si
ve:
1 - a
puede convertir el primer argumento a Number
usando el
constructor.
A veces querrá ser capaz de restringir el uso de sus operadores haciéndolos
métodos. Por ejemplo, cuando multiplique una matriz por un vector, el vector
debe ir a la derecha. Pero si quiere que sus operadores sean capaces de
convertir cualquier argumento, haga el operador una función friend
.
Afortunadamente, el compilador cogerá la expresión 1-1
y convertirá
ambos argumentos a objetos Number
y después llamará a
operator-
. Eso significaría que el código C existente podría
empezar a funcionar de forma diferente. El compilador intenta primero la
correspondencia «más simple», es decir, en este caso el operador
incorporado para la expresión 1-1
.