web-dev-qa-db-fra.com

L’opérateur << doit-il être implémenté en tant qu’ami ou en tant que fonction membre?

C’est là la question fondamentale: existe-t-il un "bon" moyen de mettre en œuvre operator<<? Lecture this Je peux voir que quelque chose comme:

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

est préféré à quelque chose comme

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

Mais je ne vois pas très bien pourquoi utiliser l'un ou l'autre.

Mon cas personnel est:

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

Mais je pourrais probablement faire:

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

Sur quelles raisons dois-je fonder cette décision?

Remarque :

 Paragraph::to_str = (return paragraph) 

où paragraphe est une chaîne.

117
Federico Builes

Le problème ici est dans votre interprétation de l'article vous lien .

Cet article concerne une personne qui rencontre des problèmes pour définir correctement les opérateurs de relation bool.

L'opérateur:

  • Egalité == et! =
  • Relation <> <=> =

Ces opérateurs doivent renvoyer une valeur booléenne car ils comparent deux objets du même type. Il est généralement plus facile de définir ces opérateurs dans le cadre de la classe. En effet, une classe est automatiquement un ami de lui-même et les objets de type Paragraphe peuvent s’examiner (même les membres privés les uns des autres).

Il existe un argument en faveur de la création de ces fonctions indépendantes car cela permet à la conversion automatique de convertir les deux côtés s'ils ne sont pas du même type, tandis que les fonctions membres permettent uniquement la conversion automatique des rhs. Je trouve cela un argument de paper man car vous ne voulez pas vraiment que la conversion automatique ait lieu en premier lieu (généralement). Mais si c'est quelque chose que vous voulez (je ne le recommande pas), il peut être avantageux de laisser les comparateurs libres.

Les opérateurs de flux:

  • opérateur << sortie
  • opérateur >> entrée

Lorsque vous les utilisez comme opérateurs de flux (plutôt que par décalage binaire), le premier paramètre est un flux. Puisque vous n'avez pas accès à l'objet stream (ce n'est pas à vous de le modifier), ceux-ci ne peuvent pas être des opérateurs membres, ils doivent être externes à la classe. Ainsi, ils doivent soit être des amis de la classe, soit avoir accès à une méthode publique permettant d'effectuer la diffusion en continu pour vous.

Il est également traditionnel pour ces objets de renvoyer une référence à un objet de flux afin que vous puissiez chaîner des opérations de flux.

#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;
}
108
Martin York

Vous ne pouvez pas le faire en tant que fonction membre, car le paramètre implicite this est le côté gauche du <<-opérateur. (Par conséquent, vous auriez besoin de l'ajouter en tant que fonction membre à la classe ostream-. Pas bien :)

Pourriez-vous le faire en tant que fonction libre sans friendle? C'est ce que je préfère, car il est clair qu'il s'agit d'une intégration avec ostream et non d'une fonctionnalité essentielle de votre classe.

52
Magnus Hoff

Si possible, en tant que fonctions non membres et non amis.

Comme le décrivent Herb Sutter et Scott Meyers, préférez les fonctions non-amis non-membres aux fonctions membres, afin d'augmenter l'encapsulation.

Dans certains cas, comme les flux C++, vous n'avez pas le choix et devez utiliser des fonctions non membres.

Mais cela ne signifie pas pour autant que vous deviez faire de ces fonctions des amis de vos classes: ces fonctions peuvent toujours accéder à votre classe par l’intermédiaire de ses accesseurs. Si vous réussissez à écrire ces fonctions de cette façon, vous avez gagné.

A propos des prototypes des opérateurs << et >>

Je crois que les exemples que vous avez donnés dans votre question sont faux. Par exemple;

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

Je ne peux même pas commencer à penser à la manière dont cette méthode pourrait fonctionner dans un flux.

Voici les deux manières d'implémenter les opérateurs << et >>.

Supposons que vous souhaitiez utiliser un objet de type T semblable à un flux.

Et que vous souhaitiez extraire/insérer de/dans T les données pertinentes de votre objet de type Paragraphe.

Opérateurs génériques << et >> prototypes de fonctions

Le premier étant en tant que fonctions:

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

Opérateurs génériques << et >> prototypes de méthodes

Le second étant en tant que méthodes:

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

Notez que pour utiliser cette notation, vous devez étendre la déclaration de classe de T. Pour les objets STL, cela n'est pas possible (vous n'êtes pas censé les modifier ...).

Et si T est un flux C++?

Voici les prototypes des mêmes opérateurs << et >> pour les flux C++.

Pour basic_istream et basic_ostream génériques

Notez que s'il s'agit de flux, comme vous ne pouvez pas modifier le flux C++, vous devez implémenter les fonctions. Ce qui signifie quelque chose comme:

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

Pour char istream et ostream

Le code suivant fonctionnera uniquement pour les flux basés sur des caractères.

// 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 a commenté le fait que le code à base de caractères n'est qu'une "spécialisation" du code générique au dessus de celui-ci. Bien sûr, Rhys a raison: je ne recommande pas l'utilisation de l'exemple basé sur des caractères. C'est seulement donné ici parce que c'est plus simple à lire. Comme il n’est viable que si vous travaillez uniquement avec des flux basés sur des caractères, vous devez l’éviter sur les plateformes où le code wchar_t est commun (c'est-à-dire sous Windows).

J'espère que cela aidera.

31
paercebal

Il devrait être implémenté en tant que fonctions libres et non-amis, surtout si, comme la plupart des choses de nos jours, la sortie est principalement utilisée pour le diagnostic et la journalisation. Ajoutez des accesseurs const pour tout ce qui doit être inséré dans la sortie, puis demandez au gestionnaire de sortie de les appeler et de les formater.

En fait, j'ai commencé à rassembler toutes ces fonctions libres de sortie ostream dans un en-tête et un fichier d'implémentation "ostreamhelpers", ce qui éloigne cette fonctionnalité secondaire du but réel des classes.

10
XPav

La signature:

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

Cela semble plutôt suspect, cela ne correspond pas à la convention stream ni à la convention au niveau des bits, de sorte que cela ressemble à un cas d'abus de surcharge de l'opérateur, operator < devrait retourner bool mais operator << devrait probablement renvoyer autre chose.

Si vous vouliez dire alors:

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

Puis, comme vous ne pouvez pas ajouter de fonctions à ostream, la fonction doit nécessairement être une fonction libre, qu’il s’agisse ou non de friend, elle dépend de ce à quoi elle doit accéder (si elle n’a pas besoin pour accéder à des membres privés ou protégés, il n’est pas nécessaire d’en faire un ami).

7
Motti

Juste pour compléter, j'aimerais ajouter que vous avez effectivement pouvez créer un opérateur ostream& operator << (ostream& os) dans une classe et que cela puisse fonctionner. D'après ce que je sais, ce n'est pas une bonne idée de l'utiliser, car c'est très compliqué et peu intuitif.

Supposons que nous avons ce code:

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

Donc, pour résumer - vous pouvez le faire, mais vous ne devriez probablement pas :)

2
ashrasmun

operator<< implémenté en tant que fonction ami:

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

} 

SORTIE: 100 Bonjour 100 Bonjour Appuyez sur n'importe quelle touche pour continuer…

Cela peut être une fonction amie uniquement parce que l'objet est du côté droit de operator<< et l'argument cout se trouvent à gauche. Donc, cela ne peut pas être une fonction membre de la classe, cela ne peut être qu'une fonction amie.

0
Rohit Vipin Mathews

opérateur ami = droits égaux à ceux de la classe

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