L'opérateur doit-il < < être mis en œuvre en tant qu'ami ou en tant que fonction membre?

StackOverflow https://stackoverflow.com/questions/236801

  •  04-07-2019
  •  | 
  •  

Question

Telle est la question fondamentale: existe-t-il un "droit"? moyen d'implémenter opérateur < < ? Lecture de 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 devrais-je utiliser l'un ou l'autre.

Mon cas personnel est le suivant:

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.

Était-ce utile?

La solution

Le problème ici réside dans votre interprétation de l'article que vous link .

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

L'opérateur:

  • Égalité == et! =
  • Relation < > < = > =

Ces opérateurs doivent renvoyer une valeur booléenne lorsqu'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 qui assurera 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;
}

Autres conseils

Vous ne pouvez pas le faire en tant que fonction membre, car le paramètre implicite est le côté gauche de l'opérateur < < . (Par conséquent, vous devrez l’ajouter en tant que fonction membre à la classe ostream . Not good:)

Pourriez-vous le faire en tant que fonction libre sans ami l'intégrer? 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.

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

Comme l'ont décrit 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é.

À propos de l'opérateur < < et > > prototypes

Je pense 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 de mettre en œuvre le < < et > > opérateurs.

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

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

Opérateur générique < < 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érateur générique < < et > > prototypes de méthodes

Le deuxième être en tant que méthode:

// 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 du même < < et > > opérateurs pour les flux C ++.

Pour les programmes basic_istream et basic_ostream

Notez qu'il s'agit de flux, car 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 les flux de caractères 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’était qu’une "spécialisation". du code générique ci-dessus. 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, évitez-le sur les plates-formes où le code wchar_t est commun (c'est-à-dire sous Windows).

J'espère que cela vous aidera.

Elle devrait être implémentée 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 les diagnostics et la journalisation. Ajoutez des accesseurs const pour tout ce qui doit entrer dans la sortie, puis faites en sorte que le gestionnaire de sortie les appelle et les formate.

J'ai en fait pris en charge de rassembler toutes ces fonctions libres de sortie ostream dans un "ostreamhelpers". En-tête et fichier d’implémentation, il maintient cette fonctionnalité secondaire loin du but réel des classes.

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 qu'il ressemble à un cas d'abus de surcharge de l'opérateur, opérateur < doit renvoyer bool mais opérateur < < devrait probablement renvoyer autre chose.

Si vous voulez dire, dites:

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

Puisqu'il est impossible d'ajouter des fonctions à ostream , la fonction doit obligatoirement être une fonction libre, qu'il s'agisse d'un ami ou non, dépend de ce à quoi elle doit accéder (s'il n'a pas besoin d'accéder à des membres privés ou protégés, vous n'avez pas besoin de vous en faire un ami).

Juste pour terminer, j'aimerais ajouter que vous pouvez créer un opérateur ostream & amp; opérateur < < (ostream & amp; os) dans une classe et cela peut 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;
}

Pour résumer, vous pouvez le faire, mais vous ne devriez probablement pas:)

opérateur < < implémenté en tant que fonction amie:

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

} 
  

OUTPUT: 100 Hello 100 Hello Appuyez sur n’importe quelle touche pour continuer & # 8230;

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

ami opérateur = 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;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top