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

C’est fondamentalement la question, existe-t-il une “bonne” manière de mettre en œuvre l’ operator<< ? En lisant ceci, 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 peux pas vraiment comprendre pourquoi devrais-je 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 quelle logique dois-je fonder cette décision?

Note :

  Paragraph::to_str = (return paragraph) 

où paragraphe est une chaîne.

Le problème ici est dans votre interprétation de l’article que vous liez .

Cet article traite de quelqu’un qui a des problèmes pour définir correctement les opérateurs de relation bool.

L’opérateur:

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

Ces opérateurs doivent renvoyer un booléen car ils comparent deux objects du même type. Il est généralement plus facile de définir ces opérateurs comme faisant partie de la classe. En effet, une classe est automatiquement un ami d’elle-même, donc les objects de type Paragraphe peuvent s’interroger (même les autres membres privés).

Il y a un argument en faveur de ces fonctions autonomes, car cela permet à la conversion automatique de convertir les deux côtés si elles ne sont pas du même type, alors que les fonctions membres ne permettent que la conversion automatique des rhs. Je trouve cela un argument de papier car vous ne voulez pas vraiment que la conversion automatique se produise 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 faire des comparateurs de façon autonome.

Les opérateurs de stream:

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

Lorsque vous les utilisez comme opérateurs de stream (plutôt que décalage binary), le premier paramètre est un stream. Comme vous n’avez pas access à l’object stream (ce n’est pas à vous de modifier), ils ne peuvent pas être des opérateurs membres, ils doivent être externes à la classe. Ils doivent donc être amis de la classe ou avoir access à une méthode publique qui fera le streaming pour vous.

Il est également habituel pour ces objects de renvoyer une référence à un object de stream afin de pouvoir enchaîner les opérations de stream.

 #include  class Paragraph { public: explicit Paragraph(std::ssortingng const& init) :m_para(init) {} std::ssortingng 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; } 

Vous ne pouvez pas le faire en tant que fonction membre, car le paramètre implicite est le côté gauche du < < -operator. (Par conséquent, vous devez l'append en tant que fonction membre à la classe ostream . Pas bon 🙂

Pourriez-vous le faire comme une fonction gratuite sans l' friend ? C'est ce que je préfère, car cela montre clairement 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 décrit par Herb Sutter et Scott Meyers, préférer les fonctions non-membres non-amis aux fonctions membres, pour aider à augmenter l’encapsulation.

Dans certains cas, comme les stream C ++, vous n’aurez pas le choix et devrez utiliser des fonctions non membres.

Mais encore, cela ne signifie pas que vous devez faire de ces fonctions des amis de vos classes: ces fonctions peuvent toujours accéder à votre classe via vos accesseurs de classe. Si vous réussissez à écrire ces fonctions de cette façon, alors vous avez gagné.

A propos de l’opérateur < < et >> prototypes

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 comment cette méthode pourrait fonctionner dans un stream.

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

Disons que vous voulez utiliser un object de type stream de type T.

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

Opérateur générique < < et >> prototypes de fonctions

Le premier étant comme 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

La seconde étant comme 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 objects STL, ce n'est pas possible (vous n'êtes pas censé les modifier ...).

Et si T est un stream C ++?

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

Pour basic_istream générique et basic_ostream

Notez que dans le cas des stream, vous ne pouvez pas modifier le stream C ++, vous devez les implémenter. Ce qui signifie quelque chose comme:

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

Pour chartrue et ostream

Le code suivant fonctionnera uniquement pour les stream 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 basé sur des caractères n'est qu'une "spécialisation" du code générique au-dessus. Bien sûr, Rhys a raison: je ne recommande pas l'utilisation de l'exemple basé sur des caractères. Il est seulement donné ici parce que c'est plus simple à lire. Comme il n'est viable que si vous ne travaillez qu'avec des stream basés sur des caractères, vous devriez l'éviter sur les plates-formes où le code wchar_t est commun (c'est-à-dire sous Windows).

J'espère que cela aidera.

Il devrait être implémenté comme une fonction gratuite et non-amie, surtout si, comme la plupart des choses, la sortie est principalement utilisée pour les diagnostics et la journalisation. Ajoutez des accesseurs const pour toutes les choses qui doivent aller dans la sortie, puis demandez au terminal de les appeler et de les formater.

J’ai effectivement pris l’habitude de rassembler toutes ces fonctions libres de sortie ostream dans un en-tête et un fichier d’implémentation “ostreamhelpers”, ce qui garde cette fonctionnalité secondaire loin du but réel des classes.

La signature:

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

Semble plutôt suspect, cela ne correspond pas à la convention de stream ni à la convention bit à bit, il semble donc que l'abus d'opérateur soit surchargé, operator < devrait renvoyer bool mais operator < < devrait probablement retourner autre chose.

Si vous voulez dire ainsi:

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

Puis, comme vous ne pouvez pas append de fonctions à ostream la fonction doit être une fonction libre, qu’elle soit ostream à un friend ou non (si elle n’a pas besoin d’accéder à des membres privés ou protégés, il n’est pas nécessaire de fais-le ami).

Juste pour terminer, je voudrais append que vous pouvez en effet créer un opérateur ostream& operator < < (ostream& os) dans une classe et que 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 ayons ce code:

 #include  #include  using namespace std; struct Widget { ssortingng name; Widget(ssortingng _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 :)

operator< < implémenté comme fonction ami:

 #include  #include  using namespace std; class Samp { public: int ID; ssortingng 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 < 

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

Cela peut être une fonction amie uniquement parce que l'object se trouve du côté droit de l' operator< < et que l'argument cout trouve du côté gauche. Donc, cela ne peut pas être une fonction membre de la classe, il ne peut s'agir que d'une fonction ami.

opérateur ami = droits égaux en tant que classe

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