¿Debería el operador << ser implementado como un amigo o como una función miembro?

Esa es básicamente la pregunta, ¿existe una forma “correcta” de implementar operator<< ? Al leer esto , puedo ver algo así como:

 friend bool operator<<(obj const& lhs, obj const& rhs); 

es preferible a algo así como

 ostream& operator<<(obj const& rhs); 

Pero no puedo ver por qué debería usar uno u otro.

Mi caso personal es:

 friend ostream & operator<<(ostream &os, const Paragraph& p) { return os << p.to_str(); } 

Pero probablemente podría hacer:

 ostream & operator<<(ostream &os) { return os << paragraph; } 

¿En qué fundamento debería basarme esta decisión?

Nota :

  Paragraph::to_str = (return paragraph) 

donde el párrafo es una cadena.

El problema aquí está en su interpretación del artículo que vincula .

Este artículo trata sobre alguien que está teniendo problemas para definir correctamente a los operadores de la relación bool.

El operador:

  • Igualdad == y! =
  • Relación <> <=> =

Estos operadores deberían devolver un bool ya que están comparando dos objetos del mismo tipo. En general, es más fácil definir estos operadores como parte de la clase. Esto se debe a que una clase es automáticamente amiga de sí misma, por lo que los objetos de tipo Párrafo pueden examinarse entre sí (incluso los miembros privados de los demás).

Existe un argumento para realizar estas funciones autónomas, ya que esto permite que la conversión automática convierta ambos lados si no son del mismo tipo, mientras que las funciones miembro solo permiten que las rhs se conviertan automáticamente. Encuentro este un argumento para el hombre de papel, ya que en realidad no quiere que la conversión automática ocurra en primer lugar (por lo general). Pero si esto es algo que desea (no lo recomiendo), entonces puede ser ventajoso que los comparadores se mantengan en pie.

Los operadores de flujo:

  • operador << salida
  • operador >> entrada

Cuando los utiliza como operadores de flujo (en lugar de desplazamiento binario), el primer parámetro es una secuencia. Como no tiene acceso al objeto de transmisión (no es suyo para modificar), estos no pueden ser operadores miembros, tienen que ser externos a la clase. Por lo tanto, deben ser amigos de la clase o tener acceso a un método público que hará la transmisión por usted.

También es tradicional que estos objetos devuelvan una referencia a un objeto de flujo para que pueda encadenar operaciones en conjunto.

 #include  class Paragraph { public: explicit Paragraph(std::string const& init) :m_para(init) {} std::string const& to_str() const { return m_para; } bool operator==(Paragraph const& rhs) const { return m_para == rhs.m_para; } bool operator!=(Paragraph const& rhs) const { // Define != operator in terms of the == operator return !(this->operator==(rhs)); } bool operator<(Paragraph const& rhs) const { return m_para < rhs.m_para; } private: friend std::ostream & operator<<(std::ostream &os, const Paragraph& p); std::string m_para; }; std::ostream & operator<<(std::ostream &os, const Paragraph& p) { return os << p.to_str(); } int main() { Paragraph p("Plop"); Paragraph q(p); std::cout << p << std::endl << (p == q) << std::endl; } 

No puede hacerlo como una función miembro, porque el parámetro implícito es el lado izquierdo del << operador. (Por lo tanto, necesitaría agregarlo como una función miembro a la clase ostream . No es bueno 🙂

¿Podrías hacerlo como una función gratuita sin tener que utilizarla? Eso es lo que prefiero, porque deja en claro que se trata de una integración con ostream , y no una funcionalidad central de su clase.

Si es posible, como funciones no miembro y no amigo.

Como describieron Herb Sutter y Scott Meyers, prefieren las funciones de no miembros que no sean amigos a las funciones de miembro, para ayudar a boost la encapsulación.

En algunos casos, al igual que las transmisiones en C ++, no podrá elegir y debe usar funciones que no sean miembro.

Pero aún así, no significa que tenga que hacer que estas funciones sean amigos de sus clases: estas funciones aún pueden acceder a su clase a través de los descriptores de acceso de su clase. Si logras escribir esas funciones de esta manera, entonces ganaste.

Acerca del operador << y >> prototipos

Creo que los ejemplos que dio en su pregunta son incorrectos. Por ejemplo;

 ostream & operator<<(ostream &os) { return os << paragraph; } 

Ni siquiera puedo empezar a pensar cómo podría funcionar este método en una transmisión.

Estas son las dos formas de implementar los operadores << y >>.

Supongamos que quiere usar un objeto de tipo secuencia de tipo T.

Y que desea extraer / insertar de / en T los datos relevantes de su objeto de tipo Párrafo.

Prototipos de función genérica operador << y >>

El primero siendo como funciones:

 // T << Paragraph T & operator << (T & p_oOutputStream, const Paragraph & p_oParagraph) { // do the insertion of p_oParagraph return p_oOutputStream ; } // T >> Paragraph T & operator >> (T & p_oInputStream, const Paragraph & p_oParagraph) { // do the extraction of p_oParagraph return p_oInputStream ; } 

Prototipos de método genérico operador << y >>

El segundo es como métodos:

 // T << Paragraph T & T::operator << (const Paragraph & p_oParagraph) { // do the insertion of p_oParagraph return *this ; } // T >> Paragraph T & T::operator >> (const Paragraph & p_oParagraph) { // do the extraction of p_oParagraph return *this ; } 

Tenga en cuenta que para usar esta notación, debe extender la statement de clase de T. Para objetos STL, esto no es posible (no se supone que los modifique ...).

¿Y qué pasa si T es una transmisión en C ++?

Aquí están los prototipos de los mismos operadores << y >> para las transmisiones en C ++.

Para basic_istream y basic_ostream generics

Tenga en cuenta que es el caso de las secuencias, ya que no puede modificar la secuencia de C ++, debe implementar las funciones. Lo que significa algo así como:

 // OUTPUT << Paragraph template  std::basic_ostream & operator << (std::basic_ostream & p_oOutputStream, const Paragraph & p_oParagraph) { // do the insertion of p_oParagraph return p_oOutputStream ; } // INPUT >> Paragraph template  std::basic_istream & operator >> (std::basic_istream & p_oInputStream, const CMyObject & p_oParagraph) { // do the extract of p_oParagraph return p_oInputStream ; } 

Para char istream y ostream

El siguiente código funcionará solo para transmisiones basadas en char.

 // OUTPUT << A std::ostream & operator << (std::ostream & p_oOutputStream, const Paragraph & p_oParagraph) { // do the insertion of p_oParagraph return p_oOutputStream ; } // INPUT >> A std::istream & operator >> (std::istream & p_oInputStream, const Paragraph & p_oParagraph) { // do the extract of p_oParagraph return p_oInputStream ; } 

Rhys Ulerich comentó sobre el hecho de que el código basado en char no es más que una "especialización" del código genérico que está sobre él. Por supuesto, Rhys tiene razón: no recomiendo el uso del ejemplo basado en char. Solo se da aquí porque es más simple de leer. Como solo es viable si solo trabaja con streams basados ​​en char, debe evitarlo en plataformas donde el código wchar_t es común (es decir, en Windows).

Espero que esto ayude

Debe implementarse como una función gratuita, sin amigos, especialmente si, como la mayoría de las cosas en estos días, la salida se utiliza principalmente para el diagnóstico y el registro. Agregue accessors const para todas las cosas que necesitan ir a la salida, y luego haga que el outputter simplemente llame a esos y haga el formateo.

De hecho, me he tomado la tarea de recostackr todas estas funciones gratuitas de salida ostream en un encabezado y archivo de implementación “ostreamhelpers”, mantiene esa funcionalidad secundaria muy alejada del objective real de las clases.

La firma:

 bool operator<<(const obj&, const obj&); 

Parece bastante sospechoso, esto no se ajusta a la convención de stream ni a la convención bit a bit así que parece un caso de abuso de sobrecarga del operador, el operator < debería devolver bool pero el operator << probablemente debería devolver algo más.

Si quisiste decirlo, di:

 ostream& operator<<(ostream&, const obj&); 

Entonces, dado que no puede agregar funciones para ostream por necesidad, la función debe ser una función gratuita, ya sea un friend o no, depende de a qué tiene que acceder (si no necesita acceder a miembros privados o protegidos, no es necesario hazlo amigo).

Solo para completar, me gustaría agregar que efectivamente puedes crear un operador ostream& operator << (ostream& os) dentro de una clase y puede funcionar. Por lo que sé, no es una buena idea usarlo, porque es muy intrincado y poco intuitivo.

Supongamos que tenemos este código:

 #include  #include  using namespace std; struct Widget { string name; Widget(string _name) : name(_name) {} ostream& operator << (ostream& os) { return os << name; } }; int main() { Widget w1("w1"); Widget w2("w2"); // These two won't work { // Error: operand types are std::ostream << std::ostream // cout << w1.operator<<(cout) << '\n'; // Error: operand types are std::ostream << Widget // cout << w1 << '\n'; } // However these two work { w1 << cout << '\n'; // Call to w1.operator<<(cout) returns a reference to ostream& w2 << w1.operator<<(cout) << '\n'; } return 0; } 

Entonces, para resumir, puedes hacerlo, pero probablemente no deberías :)

operator<< implementado como una función de amigo:

 #include  #include  using namespace std; class Samp { public: int ID; string strName; friend std::ostream& operator<<(std::ostream &os, const Samp& obj); }; std::ostream& operator<<(std::ostream &os, const Samp& obj) { os << obj.ID<< “ ” << obj.strName; return os; } int main() { Samp obj, obj1; obj.ID = 100; obj.strName = "Hello"; obj1=obj; cout << obj < 

SALIDA: 100 Hola 100 Hola Presione cualquier tecla para continuar ...

Esta puede ser una función de amigo solo porque el objeto está en el lado derecho del operator<< y el argumento cout está en el lado izquierdo. Entonces, esta no puede ser una función miembro para la clase, solo puede ser una función amiga.

amigo operador = igualdad de derechos como clase

 friend std::ostream& operator<<(std::ostream& os, const Object& object) { os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl; return os; }