Sollte der Operator << als Freund oder als Mitgliedsfunktion implementiert werden?


129

Das ist im Grunde die Frage, gibt es einen "richtigen" Weg zur Implementierung operator<<? Lesen Sie diese sehen , dass so etwas wie kann ich:

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

wird so etwas vorgezogen

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

Aber ich kann nicht genau verstehen, warum ich den einen oder anderen verwenden soll.

Mein persönlicher Fall ist:

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

Aber ich könnte wahrscheinlich tun:

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

Auf welche Gründe sollte ich diese Entscheidung stützen?

Hinweis :

 Paragraph::to_str = (return paragraph) 

Dabei ist Absatz eine Zeichenfolge.


4
Übrigens sollten Sie wahrscheinlich const zu den Signaturen der Mitgliedsfunktionen hinzufügen
Motti

4
Warum bool vom Operator << zurückgeben? Verwenden Sie es als Stream-Operator oder als Überlastung der bitweisen Verschiebung?
Martin York

Antworten:


120

Das Problem liegt hier in Ihrer Interpretation des Artikels, den Sie verlinken .

Gleichberechtigung

Dieser Artikel handelt von jemandem, der Probleme hat, die Bool-Beziehungsoperatoren korrekt zu definieren.

Der Betreiber:

  • Gleichheit == und! =
  • Beziehung <> <=> =

Diese Operatoren sollten einen Bool zurückgeben, wenn sie zwei Objekte desselben Typs vergleichen. Es ist normalerweise am einfachsten, diese Operatoren als Teil der Klasse zu definieren. Dies liegt daran, dass eine Klasse automatisch ein Freund von sich selbst ist, sodass Objekte vom Typ Paragraph sich gegenseitig untersuchen können (sogar die privaten Mitglieder des jeweils anderen).

Es gibt ein Argument für diese freistehenden Funktionen, da dadurch die automatische Konvertierung beider Seiten konvertiert werden kann, wenn sie nicht vom gleichen Typ sind, während Elementfunktionen nur die automatische Konvertierung der rhs ermöglichen. Ich finde das ein Argument von Paper Man, da Sie (normalerweise) nicht wirklich wollen, dass die automatische Konvertierung überhaupt stattfindet. Aber wenn Sie dies möchten (ich empfehle es nicht), kann es vorteilhaft sein, die Komparatoren freistehend zu machen.

Streaming

Die Stream-Betreiber:

  • Operator << Ausgabe
  • Operator >> Eingabe

Wenn Sie diese als Stream-Operatoren verwenden (anstatt als binäre Verschiebung), ist der erste Parameter ein Stream. Da Sie keinen Zugriff auf das Stream-Objekt haben (es kann nicht geändert werden), können diese keine Mitgliedsoperatoren sein. Sie müssen sich außerhalb der Klasse befinden. Daher müssen sie entweder Freunde der Klasse sein oder Zugriff auf eine öffentliche Methode haben, die das Streaming für Sie übernimmt.

Es ist auch üblich, dass diese Objekte einen Verweis auf ein Stream-Objekt zurückgeben, damit Sie Stream-Operationen miteinander verketten können.

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

19
Warum ist das operator<< private:?
Matt Clarkson

47
@ MattClarkson: Es ist nicht. Es ist eine Friend-Funktionsdeklaration, daher nicht Teil der Klasse und daher nicht von den Zugriffsspezifizierern betroffen. Im Allgemeinen setze ich die Friend-Funktionsdeklarationen neben die Daten, auf die sie zugreifen.
Martin York

12
Warum muss es eine benutzerfreundliche Funktion sein, wenn Sie die öffentliche Funktion für den Zugriff auf Daten verwenden? Entschuldigung, wenn die Frage dumm ist.
Semyon Danilov

4
@ SemyonDanilov: Warum sollten Sie die Kapselung aufheben und Getter hinzufügen? freiendist eine Möglichkeit, die öffentliche Schnittstelle zu erweitern, ohne die Kapselung zu unterbrechen. Lesen Sie programmers.stackexchange.com/a/99595/12917
Martin York

3
@LokiAstari Aber das ist sicherlich ein Argument dafür, entweder to_str zu entfernen oder es privat zu machen. Derzeit muss der Streaming-Operator kein Freund sein, da er nur öffentliche Funktionen verwendet.
Deworde

53

Sie können dies nicht als Elementfunktion ausführen , da sich der implizite thisParameter auf der linken Seite des <<Operators befindet. (Daher müssten Sie es als Member-Funktion zur ostream-class hinzufügen. Nicht gut :)

Könnten Sie es als freie Funktion tun, ohne friendes zu tun ? Das ist es, was ich bevorzuge, weil es klar macht, dass dies eine Integration ostreamund keine Kernfunktionalität Ihrer Klasse ist.


1
"Keine Kernfunktionalität Ihrer Klasse." Das bedeutet "Freund". Wenn es Kernfunktionalität wäre, wäre es in der Klasse, kein Freund.
Xaxxon

1
@xaxxon Ich denke, mein erster Satz erklärt, warum es in diesem Fall unmöglich wäre, die Funktion als Mitgliedsfunktion hinzuzufügen. Eine friendFunktion hat die gleichen Rechte als Mitglied - Funktion ( das ist , was friendbedeutet), um einen Benutzer der Klasse möchte ich Wunder, warum es das brauchen würde. Dies ist die Unterscheidung, die ich mit der Formulierung "Kernfunktionalität" treffen möchte.
Magnus Hoff

32

Wenn möglich, als Nichtmitglied und Nichtfreund.

Wie von Herb Sutter und Scott Meyers beschrieben, bevorzugen Sie Funktionen von Nicht-Freunden, die keine Mitglieder sind, gegenüber Funktionen von Mitgliedern, um die Kapselung zu verbessern.

In einigen Fällen, wie bei C ++ - Streams, haben Sie keine Wahl und müssen Funktionen verwenden, die keine Mitglieder sind.

Dies bedeutet jedoch nicht, dass Sie diese Funktionen zu Freunden Ihrer Klassen machen müssen: Diese Funktionen können Ihre Klasse weiterhin über Ihre Klassenzugriffsberechtigten erreichen. Wenn es Ihnen gelingt, diese Funktionen auf diese Weise zu schreiben, haben Sie gewonnen.

Über Operator << und >> Prototypen

Ich glaube, die Beispiele, die Sie in Ihrer Frage gegeben haben, sind falsch. Beispielsweise;

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

Ich kann mir gar nicht vorstellen, wie diese Methode in einem Stream funktionieren könnte.

Hier sind die beiden Möglichkeiten, die Operatoren << und >> zu implementieren.

Angenommen, Sie möchten ein Stream-ähnliches Objekt vom Typ T verwenden.

Und dass Sie die relevanten Daten Ihres Objekts vom Typ Paragraph aus / in T extrahieren / einfügen möchten.

Generische Operator << und >> Funktionsprototypen

Das erste ist als Funktionen:

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

Generische Prototypen der Operatormethoden << und >>

Das zweite ist als Methoden:

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

Beachten Sie, dass Sie zur Verwendung dieser Notation die Klassendeklaration von T erweitern müssen. Für STL-Objekte ist dies nicht möglich (Sie sollten sie nicht ändern ...).

Und was ist, wenn T ein C ++ - Stream ist?

Hier sind die Prototypen derselben << und >> Operatoren für C ++ - Streams.

Für generischen basic_istream und basic_ostream

Beachten Sie, dass dies bei Streams der Fall ist, da Sie den C ++ - Stream nicht ändern können. Sie müssen die Funktionen implementieren. Was so etwas bedeutet wie:

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

Für char istream und ostream

Der folgende Code funktioniert nur für char-basierte Streams.

// 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 kommentierte die Tatsache, dass der char-basierte Code nur eine "Spezialisierung" des darüber liegenden generischen Codes ist. Natürlich hat Rhys Recht: Ich empfehle die Verwendung des char-basierten Beispiels nicht. Es wird hier nur gegeben, weil es einfacher zu lesen ist. Da dies nur möglich ist, wenn Sie nur mit char-basierten Streams arbeiten, sollten Sie dies auf Plattformen vermeiden, auf denen wchar_t-Code häufig vorkommt (z. B. unter Windows).

Hoffe das wird helfen.


Deckt Ihr generischer Vorlagencode für basic_istream und basic_ostream nicht bereits die std :: ostream- und std :: istream-spezifischen Versionen ab, da die beiden letzteren nur Instanziierungen der ersteren mit Zeichen sind?
Rhys Ulerich

@ Rhys Ulerich: Natürlich. Ich verwende nur die generische Vorlagenversion, schon allein deshalb, weil Sie unter Windows sowohl mit char- als auch mit wchar_t-Code umgehen müssen. Der einzige Vorteil der zweiten Version besteht darin, einfacher als die erste zu erscheinen. Ich werde meinen Beitrag darüber klarstellen.
Paercebal

10

Es sollte als kostenlose, nicht befreundete Funktion implementiert werden, insbesondere wenn, wie die meisten Dinge heutzutage, die Ausgabe hauptsächlich für Diagnose und Protokollierung verwendet wird. Fügen Sie const-Accessoren für alle Dinge hinzu, die in die Ausgabe aufgenommen werden müssen, und lassen Sie den Outputter diese einfach aufrufen und formatieren.

Ich habe es mir zur Aufgabe gemacht, all diese ostream-ausgabefreien Funktionen in einem "ostreamhelpers" -Header und einer Implementierungsdatei zu sammeln. Dadurch wird diese sekundäre Funktionalität weit vom eigentlichen Zweck der Klassen entfernt.


7

Die Unterschrift:

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

Scheint eher verdächtig zu sein, dies passt weder zur streamKonvention noch zur bitweisen Konvention, so dass es so aussieht, als würde ein Missbrauch des Bedieners überladen, operator <sollte zurückkehren , sollte boolaber operator <<wahrscheinlich etwas anderes zurückgeben.

Wenn du es so gemeint hast, sag:

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

Da Sie dann nicht ostreamunbedingt Funktionen hinzufügen können, muss die Funktion eine freie Funktion sein, ob sie eine ist friendoder nicht, hängt davon ab, auf was sie zugreifen muss (wenn sie nicht auf private oder geschützte Mitglieder zugreifen muss, muss sie nicht erstellt werden Freund).


Es ist erwähnenswert, ostreamdass bei Verwendung der ostream.operator<<(obj&)Bestellung ein Zugriff zum Ändern erforderlich ist . daher die freie Funktion. Andernfalls muss der Benutzertyp ein Dampftyp sein, um den Zugriff zu ermöglichen.
Wulfgarpro

2

Nur zur Vervollständigung möchte ich hinzufügen, dass Sie in der Tat einen Operator ostream& operator << (ostream& os)innerhalb einer Klasse erstellen können und dieser funktionieren kann. Soweit ich weiß, ist es keine gute Idee, es zu verwenden, da es sehr kompliziert und nicht intuitiv ist.

Nehmen wir an, wir haben diesen 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;
}

Um es zusammenzufassen - Sie können es tun, aber Sie sollten es höchstwahrscheinlich nicht tun :)


0

Freund Operator = gleiche Rechte als Klasse

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

0

operator<< als Freundfunktion implementiert:

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

} 

AUSGABE:
100 Hallo
100 Hallo

Dies kann nur eine Friend-Funktion sein, da sich das Objekt auf der rechten Seite operator<<und das Argument coutauf der linken Seite befindet. Dies kann also keine Mitgliedsfunktion der Klasse sein, sondern nur eine Freundfunktion.


Ich glaube nicht, dass es eine Möglichkeit gibt, dies als Mitgliederfunktion zu schreiben !!
Rohit Vipin Mathews

Warum ist alles mutig. Lassen Sie mich das entfernen.
Sebastian Mach
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.