12.4. Operadores no miembros

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.

12.4.1. Directrices básicas

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




[70] Rob Murray, C++ Strategies & Tactics , Addison Wesley, 1993, pagina 47.