En algunos de los ejemplos anteriores, los operadores pueden ser miembros o no, y no parece haber mucha diferencia. Esto normalmente provoca la pregunta, «¿Cuál debería elegir?». En general, si no hay ninguna diferencia deberían ser miembros, para enfatizar la asociación entre el operador y su clase. Cuando el operando de la izquierda es siempre un objeto de la clase actual funciona bien.
Sin embargo, a veces querrá que el operando de la izquierda sea un objeto de alguna
otra clase. Un caso típico en el que ocurre eso es cuando se sobrecargan los
operadores <<
y >>
para los flujos de
entrada/salida. Dado que estos flujos son una librería fundamental en C++,
probablemente querrá sobrecargar estos operadores para la mayoría de sus clases, por
eso el proceso es digno de tratarse:
//: C12:IostreamOperatorOverloading.cpp // Example of non-member overloaded operators #include "../require.h" #include <iostream> #include <sstream> // "String streams" #include <cstring> using namespace std; class IntArray { enum { sz = 5 }; int i[sz]; public: IntArray() { memset(i, 0, sz* sizeof(*i)); } int& operator[](int x) { require(x >= 0 && x < sz, "IntArray::operator[] out of range"); return i[x]; } friend ostream& operator<<(ostream& os, const IntArray& ia); friend istream& operator>>(istream& is, IntArray& ia); }; ostream& operator<<(ostream& os, const IntArray& ia) { for(int j = 0; j < ia.sz; j++) { os << ia.i[j]; if(j != ia.sz -1) os << ", "; } os << endl; return os; } istream& operator>>(istream& is, IntArray& ia){ for(int j = 0; j < ia.sz; j++) is >> ia.i[j]; return is; } int main() { stringstream input("47 34 56 92 103"); IntArray I; input >> I; I[4] = -1; // Use overloaded operator[] cout << I; } ///:~
Listado 12.12. C12/IostreamOperatorOverloading.cpp
Esta clase contiene también un operador sobrecargado operator[]
el cual
devuelve una referencia a un valor legítimo en el array. Dado que devuelve una
referencia, la expresión:
I[4] = -1;
No sólo parece mucho más adecuada que si se usaran punteros, también causa el efecto deseado.
Es importante que los operadores de desplazamiento sobrecargados se pasen y devuelvan por referencia, para que los cambios afecten a los objetos externos. En las definiciones de las funciones, expresiones como:
os << ia.i[j];
provocan que sean llamadas las funciones de los operadores sobrecargados
(esto es, aquellas definidas en iostream
). En este
caso, la función llamada es ostream&
operator<<(ostream&, int)
dado que
ia[i].j
se resuelve a int
.
Una vez que las operaciones se han realizado en
istream
o en ostream
se
devuelve para que se pueda usaren expresiones más complicadas.
En main()
se usa un nuevo tipo de
iostream
: el stringstream
(declarado
en <sstream>
). Es una clase que toma una cadena
(que se puede crear de un array de char
, como se ve aquí) y
lo convierte en un iostream
. En el ejemplo de
arriba, esto significa que los operadores de desplazamiento pueden ser
comprobados sin abrir un archivo o sin escribir datos en la línea de
comandos.
La manera mostrada en este ejemplo para el extractor y el insertador es estándar. Si quiere crear estos operadores para su propia clase, copie el prototipo de la función y los tipos de retorno de arriba y siga el estilo del cuerpo.
Murray [70] sugiere estas reglas de estilo para elegir entre miembros y no miembros:
Operador | Uso recomendado |
---|---|
Todos los operadores unarios | miembro |
= () [] -> ->* | debe ser miembro |
+= -= /= *= ^= &= |= %= >>= <<= | miembro |
El resto de operadores binarios | no miembro |
Tabla 12.1. Directrices para elegir entre miembro y no-miembro