Pregunta

Esa es básicamente la pregunta, ¿hay un " derecho " forma de implementar operator < < ? Leyendo esto puedo ver que algo así como:

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

se prefiere a algo como

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

Pero no puedo entender 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é razón debo basar esta decisión?

Nota :

 Paragraph::to_str = (return paragraph) 

donde el párrafo es una cadena.

¿Fue útil?

Solución

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

Este artículo trata sobre alguien que tiene problemas para definir correctamente los operadores de 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. Por lo 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 demás miembros privados).

Existe un argumento para realizar estas funciones independientes, 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 los rhs se conviertan automáticamente. Considero que este es un argumento de hombre de papel, ya que en realidad no desea que se produzca la conversión automática (por lo general). Pero si esto es algo que desea (no lo recomiendo), hacer que los comparadores estén libres puede ser ventajoso.

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 un flujo. Como no tiene acceso al objeto de flujo (no es suyo modificarlo), estos no pueden ser operadores miembros, deben ser externos a la clase. Por lo tanto, deben ser amigos de la clase o tener acceso a un método público que haga la transmisión por usted.

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

#include <iostream>

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;
}

Otros consejos

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

¿Podría hacerlo como una función gratuita sin friend ? 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 lo describieron Herb Sutter y Scott Meyers, prefieren las funciones que no son miembros no amigos a las funciones miembros, para ayudar a aumentar la encapsulación.

En algunos casos, como las transmisiones de C ++, no tendrá la opción y deberá usar funciones que no sean miembros.

Pero aún así, no significa que tenga que hacer que estas funciones sean amigas de sus clases: estas funciones aún pueden acceder a su clase a través de sus accesos de clase. Si logra escribir esas funciones de esta manera, entonces ganó.

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 secuencia.

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

Supongamos que desea utilizar un objeto similar al flujo del tipo T.

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

Operador genérico < < y > > prototipos de funciones

El primero es 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 ;
}

Operador genérico < < y > > prototipos de método

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 declaración de clase de T. Para los objetos STL, esto no es posible (se supone que no debe modificarlos ...).

¿Y si T es un flujo de C ++?

Aquí están los prototipos del mismo < < y > > operadores para flujos de C ++.

Para basic_istream y basic_ostream genéricos

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 como:

// OUTPUT << Paragraph
template <typename charT, typename traits>
std::basic_ostream<charT,traits> & operator << (std::basic_ostream<charT,traits> & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// INPUT >> Paragraph
template <typename charT, typename traits>
std::basic_istream<charT,traits> & operator >> (std::basic_istream<charT,traits> & 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 caracteres.

// 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 caracteres no es más que una "especialización". del código genérico encima de él. Por supuesto, Rhys tiene razón: no recomiendo el uso del ejemplo basado en char. Solo se da aquí porque es más fácil de leer. Como solo es viable si solo trabaja con transmisiones basadas 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, no amigable, especialmente si, como la mayoría de las cosas en estos días, la salida se usa principalmente para diagnósticos y registros. Agregue accesos constantes para todas las cosas que necesitan ir a la salida, y luego haga que el emisor solo llame a esos y realice el formateo.

De hecho, he empezado a recopilar todas estas funciones libres de salida de ostream en un '' ostreamhelpers '' encabezado y archivo de implementación, mantiene esa funcionalidad secundaria lejos del propósito real de las clases.

La firma:

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

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

Si quisiste decirlo, di:

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

Entonces, dado que no puede agregar funciones a ostream por necesidad, la función debe ser una función libre, ya sea friend o no, depende de a qué tenga acceso (si no necesita acceder a miembros privados o protegidos, no hay necesidad de hacerlo amigo).

Solo por completar, me gustaría agregar que de hecho puede crear un operador ostream & amp; operador < < (ostream & amp; os) dentro de una clase y puede funcionar. Por lo que sé, no es una buena idea usarlo, porque es muy complicado y poco intuitivo.

Supongamos que tenemos este código:

#include <iostream>
#include <string>

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, puede hacerlo, pero probablemente no debería :)

El

operador < < implementado como una función amiga:

#include <iostream>
#include <string>
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 <<endl<< obj1;

} 
  

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

Esta puede ser una función amiga solo porque el objeto está en el lado derecho del operador < < y el argumento cout está en el lado izquierdo. Entonces, esta no puede ser una función miembro de 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;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top