L’operatore deve essere implementato come amico o come membro?

Questa è fondamentalmente la domanda, c‘è un modo “giusto” per implementare l’ operator<< ? Leggendo questo posso vedere che qualcosa di simile:

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

è preferito a qualcosa di simile

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

Ma non riesco a capire perché dovrei usare l’uno o l’altro.

Il mio caso personale è:

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

Ma potrei probabilmente fare:

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

A quale logica dovrei basare questa decisione?

Nota :

  Paragraph::to_str = (return paragraph) 

dove il paragrafo è una stringa.

Il problema qui è nella tua interpretazione dell’articolo che colleghi .

Questo articolo riguarda qualcuno che ha problemi nel definire correttamente gli operatori di relazioni bool.

L’operatore:

  • Uguaglianza == e! =
  • Relazione <> < => =

Questi operatori dovrebbero restituire un valore bool mentre confrontano due oggetti dello stesso tipo. In genere è più semplice definire questi operatori come parte della class. Questo perché una class è automaticamente un amico di sé, quindi gli oggetti di tipo Paragrafo possono esaminarsi a vicenda (anche gli altri membri privati).

C’è un argomento per fare queste funzioni free standing in quanto consente alla conversione automatica di convertire entrambi i lati se non sono dello stesso tipo, mentre le funzioni dei membri consentono solo la conversione automatica dei rhs. Trovo che questo sia un argomento uomo di carta, in quanto in realtà non si desidera che la conversione automatica avvenga in primo luogo (di solito). Ma se questo è qualcosa che vuoi (non lo consiglio), rendere i comparatori indipendenti può essere vantaggioso.

Gli operatori del stream:

  • operatore < < uscita
  • operatore >> input

Quando li si utilizza come operatori di stream (anziché come shift binario), il primo parametro è un stream. Dato che non hai accesso all’object stream (non è il tuo da modificare) questi non possono essere operatori membri devono essere esterni alla class. Quindi devono essere amici della class o avere accesso a un metodo pubblico che farà lo streaming per te.

È anche tradizionale per questi oggetti restituire un riferimento a un object stream in modo da poter concatenare le operazioni dello stream.

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

Non è ansible farlo come funzione membro, poiché l’implicito this parametro è il lato sinistro del < < -operator. (Quindi, dovresti aggiungerlo come funzione membro alla class ostream . Non va bene 🙂

Potresti farlo come una funzione gratuita senza averlo? È ciò che preferisco, perché chiarisce che si tratta di un'integrazione con ostream e non una funzionalità di base della class.

Se ansible, come funzioni non membro e non amico.

Come descritto da Herb Sutter e Scott Meyers, preferiscono le funzioni non membro non amico alle funzioni membro, per contribuire ad aumentare l’incapsulamento.

In alcuni casi, come gli stream di C ++, non avrai la scelta e dovrai usare le funzioni non membro.

Tuttavia, ciò non significa che devi rendere queste funzioni amiche delle tue classi: queste funzioni possono ancora accedere alla tua class tramite i tuoi strumenti di accesso alla class. Se riesci a scrivere queste funzioni in questo modo, allora hai vinto.

Informazioni sugli operatori < < e >> prototipi

Credo che gli esempi che hai fornito nella tua domanda siano sbagliati. Per esempio;

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

Non riesco nemmeno a cominciare a pensare a come questo metodo potrebbe funzionare in un stream.

Ecco i due modi per implementare gli operatori < < e >>.

Supponiamo che tu voglia utilizzare un object simile al stream di tipo T.

E che vuoi estrarre / inserire da / in T i dati rilevanti del tuo object di tipo Paragrafo.

Operatore generico < < e >> prototipi di funzioni

Il primo è come funzioni:

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

Operatore generico < < e >> prototipi di metodo

Il secondo è come metodi:

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

Nota che per usare questa notazione devi estendere la dichiarazione di class di T. Per gli oggetti STL, questo non è ansible (non è necessario modificarli ...).

E se T fosse un stream C ++?

Ecco i prototipi degli stessi operatori < < e >> per gli stream C ++.

Per generico basic_istream e basic_ostream

Nota che è il caso degli stream, dato che non puoi modificare il stream C ++, devi implementare le funzioni. Il che significa qualcosa come:

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

Per char istream e ostream

Il seguente codice funzionerà solo per i flussi basati su 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 ha commentato sul fatto che il codice basato sul char è solo una "specializzazione" del codice generico sopra di esso. Naturalmente, Rhys ha ragione: non raccomando l'uso dell'esempio basato su char. Viene dato solo qui perché è più semplice da leggere. Poiché è valido solo se lavori solo con flussi basati su char, dovresti evitarlo su piattaforms in cui il codice wchar_t è comune (cioè su Windows).

Spero che questo ti sia d'aiuto.

Dovrebbe essere implementato come una funzione libera, non amica, soprattutto se, come la maggior parte delle cose in questi giorni, l’output viene utilizzato principalmente per la diagnostica e la registrazione. Aggiungi i metodi di accesso const per tutte le cose che devono essere inserite nell’output, quindi fai in modo che l’outputter li chiami e faccia la formattazione.

Ho effettivamente preso la raccolta di tutte queste funzioni free di ostream in un’intestazione e un file di implementazione di “ostreamhelpers”, mantiene quella funzionalità secondaria lontana dallo scopo reale delle classi.

La firma:

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

Sembra piuttosto sospetto, questo non si adatta alla convenzione di stream né alla convenzione bit a bit, quindi sembra un caso di abuso da parte operator < , l' operator < dovrebbe restituire bool ma l' operator < < dovrebbe probabilmente restituire qualcos'altro.

Se volevi dire così:

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

Quindi, poiché non è ansible aggiungere funzioni a ostream la funzione deve essere una funzione libera, indipendentemente dal fatto che si tratti di un friend o meno dipende da cosa deve accedere (se non è necessario accedere a membri privati ​​o protetti non è necessario renderlo amico).

Per completezza, vorrei aggiungere che in effetti puoi creare un operatore ostream& operator < < (ostream& os) all'interno di una class e può funzionare. Da quello che so non è una buona idea usarlo, perché è molto contorto e non intuitivo.

Supponiamo di avere questo codice:

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

Quindi riassumendo: puoi farlo, ma probabilmente non dovresti farlo :)

operator< < implementato come funzione amico:

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

USCITA: 100 Ciao 100 Ciao Premi un tasto qualsiasi per continuare ...

Questa può essere una funzione amico solo perché l'object si trova sul lato destro operator< < e l'argomento cout trova sul lato sinistro. Quindi questa non può essere una funzione membro della class, può essere solo una funzione di amicizia.

operatore amico = pari diritti come class

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