Wie kann man den Operator << für einen Ostream richtig überladen?

237

Ich schreibe eine kleine Matrixbibliothek in C ++ für Matrixoperationen. Mein Compiler beschwert sich jedoch, wo es vorher nicht war. Dieser Code wurde 6 Monate lang in einem Regal belassen und dazwischen habe ich meinen Computer von Debian Etch auf Lenny (g ++ (Debian 4.3.2-1.1) 4.3.2) aktualisiert. Auf einem Ubuntu-System mit demselben g ++ habe ich jedoch das gleiche Problem .

Hier ist der relevante Teil meiner Matrixklasse:

namespace Math
{
    class Matrix
    {
    public:

        [...]

        friend std::ostream& operator<< (std::ostream& stream, const Matrix& matrix);
    }
}

Und die "Implementierung":

using namespace Math;

std::ostream& Matrix::operator <<(std::ostream& stream, const Matrix& matrix) {

    [...]

}

Dies ist der vom Compiler angegebene Fehler:

matrix.cpp: 459: Fehler: 'std :: ostream & Math :: Matrix :: operator << (std :: ostream &, const Math :: Matrix &)' muss genau ein Argument annehmen

Ich bin ein bisschen verwirrt von diesem Fehler, aber andererseits ist mein C ++ etwas rostig geworden, nachdem ich in diesen 6 Monaten viel Java gemacht habe. :-)

Matthias van der Vlies
quelle

Antworten:

127

Sie haben Ihre Funktion als deklariert friend. Es ist kein Mitglied der Klasse. Sie sollten Matrix::aus der Implementierung entfernen . friendbedeutet, dass die angegebene Funktion (die kein Mitglied der Klasse ist) auf private Mitgliedsvariablen zugreifen kann. Die Art und Weise, wie Sie die Funktion implementiert haben, ähnelt einer Instanzmethode für eine MatrixKlasse, die falsch ist.

Mehrdad Afshari
quelle
7
Und Sie sollten es auch im Math-Namespace deklarieren (nicht nur mit einem using-Namespace Math).
David Rodríguez - Dribeas
1
Warum muss das operator<<im Namespace von sein Math? Es scheint, dass es im globalen Namespace sein sollte. Ich bin damit einverstanden, dass mein Compiler möchte, dass es im Namespace von ist Math, aber das macht für mich keinen Sinn.
Mark Lakata
Entschuldigung, aber ich verstehe nicht, warum wir hier dann das Schlüsselwort friend verwenden. Wenn in einer Klasse die Überschreibung von Friend-Operatoren deklariert wird, können wir sie anscheinend nicht mit Matrix :: operator << (ostream & os, const Matrix & m) implementieren. Stattdessen müssen wir nur den globalen Operator-Override-Operator << ostream & os, const Matrix & m) verwenden. Warum also überhaupt die Mühe machen, ihn innerhalb der Klasse zu deklarieren?
Patrick
139

Ich erzähle Ihnen nur von einer anderen Möglichkeit: Ich verwende dafür gerne Freundedefinitionen:

namespace Math
{
    class Matrix
    {
    public:

        [...]

        friend std::ostream& operator<< (std::ostream& stream, const Matrix& matrix) {
            [...]
        }
    };
}

Die Funktion wird automatisch auf den umgebenden Namespace ausgerichtet Math(obwohl ihre Definition im Bereich dieser Klasse angezeigt wird), ist jedoch nur sichtbar, wenn Sie den Operator << mit einem Matrix-Objekt aufrufen, wodurch die argumentabhängige Suche diese Operatordefinition findet. Dies kann manchmal bei mehrdeutigen Aufrufen hilfreich sein, da es für andere Argumenttypen als Matrix nicht sichtbar ist. Wenn Sie die Definition schreiben, können Sie auch direkt auf in Matrix definierte Namen und auf Matrix selbst verweisen, ohne den Namen mit einem möglicherweise langen Präfix zu qualifizieren und Vorlagenparameter wie bereitzustellen Math::Matrix<TypeA, N>.

Johannes Schaub - litb
quelle
77

Um Mehrdad Antwort hinzuzufügen,

namespace Math
{
    class Matrix
    {
       public:

       [...]


    }   
    std::ostream& operator<< (std::ostream& stream, const Math::Matrix& matrix);
}

In Ihrer Implementierung

std::ostream& operator<<(std::ostream& stream, 
                     const Math::Matrix& matrix) {
    matrix.print(stream); //assuming you define print for matrix 
    return stream;
 }
kal
quelle
4
Ich verstehe nicht, warum dies eine Abwärtsabstimmung ist. Dies verdeutlicht, dass Sie den Operator als im Namespace und nicht einmal als Freund deklarieren können und wie Sie möglicherweise den Operator deklarieren können.
Kal
2
Die Antwort von Mehrdad enthielt keinen Codeausschnitt, daher habe ich nur hinzugefügt, was möglicherweise funktioniert, indem ich ihn im Namespace selbst außerhalb der Klasse verschiebe.
Kal
Ich verstehe Ihren Standpunkt, ich habe nur Ihren zweiten Ausschnitt angesehen. Aber jetzt sehe ich, dass Sie den Operator aus der Klasse genommen haben. Danke für den Vorschlag.
Matthias van der Vlies
7
Es gehört nicht nur nicht zur Klasse, sondern ist auch im Math-Namespace richtig definiert . Es hat auch den zusätzlichen Vorteil (möglicherweise nicht für eine Matrix, aber mit anderen Klassen), dass "Drucken" virtuell sein kann und das Drucken daher auf der am meisten abgeleiteten Vererbungsebene erfolgt.
David Rodríguez - Dribeas
68

Angenommen, es handelt sich um eine Überladung operator <<für alle Klassen, von denen abgeleitet wurde std::ostream, um die MatrixKlasse zu behandeln (und nicht um eine Überladung <<für eine MatrixKlasse), ist es sinnvoller, die Überladungsfunktion außerhalb des Math-Namespace im Header zu deklarieren.

Verwenden Sie eine Friend-Funktion nur, wenn die Funktionalität nicht über die öffentlichen Schnittstellen erreicht werden kann.

Matrix.h

namespace Math { 
    class Matrix { 
        //...
    };  
}
std::ostream& operator<<(std::ostream&, const Math::Matrix&);

Beachten Sie, dass die Operatorüberladung außerhalb des Namespace deklariert wird.

Matrix.cpp

using namespace Math;
using namespace std;

ostream& operator<< (ostream& os, const Matrix& obj) {
    os << obj.getXYZ() << obj.getABC() << '\n';
    return os;
}

Wenn andererseits Ihre Überlastungsfunktion hergestellt werden muss, benötigt ein Freund Zugriff auf private und geschützte Mitglieder.

Math.h

namespace Math {
    class Matrix {
        public:
            friend std::ostream& operator<<(std::ostream&, const Matrix&);
    };
}

Sie müssen die Funktionsdefinition nicht nur mit einem Namespace-Block versehen using namespace Math;.

Matrix.cpp

using namespace Math;
using namespace std;

namespace Math {
    ostream& operator<<(ostream& os, const Matrix& obj) {
        os << obj.XYZ << obj.ABC << '\n';
        return os;
    }                 
}
sanjivr
quelle
38

In C ++ 14 können Sie die folgende Vorlage verwenden, um jedes Objekt zu drucken, das eine T :: print (std :: ostream &) const; Mitglied.

template<class T>
auto operator<<(std::ostream& os, T const & t) -> decltype(t.print(os), os) 
{ 
    t.print(os); 
    return os; 
} 

In C ++ 20 können Konzepte verwendet werden.

template<typename T>
concept Printable = requires(std::ostream& os, T const & t) {
    { t.print(os) };
};

template<Printable T>
std::ostream& operator<<(std::ostream& os, const T& t) { 
    t.print(os); 
    return os; 
} 
QuentinUK
quelle
interessante Lösung! Eine Frage: Wo soll dieser Operator wie in einem globalen Bereich deklariert werden? Ich gehe davon aus, dass es für alle Typen sichtbar sein sollte, die zur Vorlage verwendet werden können.
Barney
@barney Es könnte sich in Ihrem eigenen Namespace befinden, zusammen mit den Klassen, die es verwenden.
QuentinUK
Kannst du nicht einfach zurückkehren std::ostream&, da es sowieso der Rückgabetyp ist?
Jean-Michaël Celerier
5
@ Jean-MichaëlCelerier Der decltype stellt sicher, dass dieser Operator nur verwendet wird, wenn t :: print vorhanden ist. Andernfalls würde versucht, den Funktionskörper zu kompilieren und einen Kompilierungsfehler auszugeben.
QuentinUK
Konzeptversion
QuentinUK