Jak poprawnie przeciążyć operatora << dla ostreamu?

237

Piszę małą bibliotekę macierzy w C ++ do operacji na macierzach. Jednak mój kompilator narzeka, a wcześniej tego nie robił. Ten kod pozostawiono na półce przez 6 miesięcy, aw międzyczasie uaktualniłem komputer z debian etch do lenny (g ++ (Debian 4.3.2-1.1) 4.3.2), jednak mam ten sam problem w systemie Ubuntu z tym samym g ++ .

Oto odpowiednia część mojej klasy macierzowej:

namespace Math
{
    class Matrix
    {
    public:

        [...]

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

I „wdrożenie”:

using namespace Math;

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

    [...]

}

Oto błąd podany przez kompilator:

matrix.cpp: 459: error: 'std :: ostream & Math :: Matrix :: operator << (std :: ostream &, const Math :: Matrix &)' musi wziąć dokładnie jeden argument

Jestem trochę zdezorientowany tym błędem, ale z drugiej strony mój C ++ trochę się zardzewiał po tym, jak dużo Java napisałem przez te 6 miesięcy. :-)

Matthias van der Vlies
źródło

Odpowiedzi:

127

Zadeklarowałeś swoją funkcję jako friend. To nie jest członek klasy. Powinieneś usunąć Matrix::z wdrożenia. friendoznacza, że ​​określona funkcja (która nie jest członkiem klasy) może uzyskać dostęp do zmiennych prywatnych członków. Sposób, w jaki zaimplementowałeś funkcję, jest jak metoda instancji dla Matrixklasy, która jest zła.

Mehrdad Afshari
źródło
7
Powinieneś także zadeklarować go w przestrzeni nazw Math (nie tylko za pomocą używanej przestrzeni nazw Math).
David Rodríguez - dribeas
1
Dlaczego operator<<muszą być w przestrzeni nazw Math? Wydaje się, że powinien on znajdować się w globalnej przestrzeni nazw. Zgadzam się, że mój kompilator chce, aby był w przestrzeni nazw Math, ale to nie ma dla mnie sensu.
Mark Lakata
Przepraszam, ale nie rozumiem, dlaczego używamy tutaj słowa kluczowego „przyjaciel”? Kiedy deklarujemy zastąpienie operatora przyjaciela w klasie, wydaje się, że nie możemy zaimplementować z Matrix :: operator << (ostream & os, const Matrix & m). Zamiast tego musimy po prostu użyć operatora przesłonięcia operatora globalnego << ostream & os, const Matrix & m), więc po co w ogóle zadawać sobie trud, aby zadeklarować to w klasie?
Patrick
139

Mówię tylko o jednej innej możliwości: lubię do tego używać definicji znajomych:

namespace Math
{
    class Matrix
    {
    public:

        [...]

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

Funkcja zostanie automatycznie skierowana do otaczającej przestrzeni nazw Math(nawet jeśli jej definicja pojawia się w zakresie tej klasy), ale nie będzie widoczna, dopóki nie wywołasz operatora << za pomocą obiektu Matrix, który sprawi, że wyszukiwanie zależne od argumentów znajdzie tę definicję operatora. Może to czasem pomóc w niejednoznacznych wywołaniach, ponieważ jest niewidoczne dla typów argumentów innych niż Matrix. Pisząc jego definicję, możesz również odwoływać się bezpośrednio do nazw zdefiniowanych w Matrycy i do samej Matrycy, bez kwalifikowania nazwy jakimś długim prefiksem i podawania parametrów szablonu, takich jak Math::Matrix<TypeA, N>.

Johannes Schaub - litb
źródło
77

Aby dodać do odpowiedzi Mehrdad,

namespace Math
{
    class Matrix
    {
       public:

       [...]


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

W twojej realizacji

std::ostream& operator<<(std::ostream& stream, 
                     const Math::Matrix& matrix) {
    matrix.print(stream); //assuming you define print for matrix 
    return stream;
 }
kal
źródło
4
Nie rozumiem, dlaczego jest to głosowanie negatywne, wyjaśnia to, że możesz zadeklarować operatora, aby znajdował się w przestrzeni nazw, a nawet nie jako przyjaciela, i jak możesz zadeklarować operatora.
kal
2
Odpowiedź Mehrdada nie zawierała żadnego fragmentu kodu, więc właśnie dodałem, co może działać, przenosząc go poza klasę w samej przestrzeni nazw.
kal
Rozumiem twój punkt widzenia, patrzyłem tylko na twój drugi fragment. Ale teraz widzę, że usunąłeś operatora z klasy. Dzieki za sugestie.
Matthias van der Vlies
7
Nie tylko jest poza klasą, ale jest poprawnie zdefiniowany w przestrzeni nazw Math. Ma także dodatkową zaletę (być może nie w przypadku Matrycy, ale w przypadku innych klas), że „druk” może być wirtualny, a zatem drukowanie nastąpi na najbardziej pochodnym poziomie dziedziczenia.
David Rodríguez - dribeas
68

Zakładając, że mówimy o przeciążeniu operator <<dla wszystkich klas pochodnych od std::ostreamobsłużyć Matrixklasy (a nie przeciążenia <<dlaMatrix klasy), sensowniejsze jest zadeklarowanie funkcji przeciążenia poza przestrzenią nazw Math w nagłówku.

Użyj funkcji przyjaciela tylko wtedy, gdy nie można uzyskać tej funkcji za pomocą publicznych interfejsów.

Matrix.h

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

Pamiętaj, że przeciążenie operatora jest zadeklarowane poza przestrzenią nazw.

Matrix.cpp

using namespace Math;
using namespace std;

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

Z drugiej strony, jeśli funkcja przeciążenia robi muszą być wykonane znajomego tj potrzebuje dostępu do członków prywatnych i chronionych.

Math.h

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

Musisz zawrzeć definicję funkcji w bloku przestrzeni nazw zamiast po prostu 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
źródło
38

W C ++ 14 możesz użyć następującego szablonu do wydrukowania dowolnego obiektu, który ma const T :: print (std :: ostream &); członek.

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

W C ++ 20 można używać pojęć.

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
źródło
ciekawe rozwiązanie! Jedno pytanie - gdzie należy zadeklarować tego operatora, np. W zakresie globalnym? Zakładam, że powinien być widoczny dla wszystkich typów, których można użyć do tworzenia szablonów?
barney
@barney Może to być Twoja własna przestrzeń nazw wraz z klasami, które z niej korzystają.
QuentinUK
nie możesz po prostu wrócić std::ostream&, skoro i tak jest to typ zwrotu?
Jean-Michaël Celerier
5
@ Jean-MichaëlCelerier Decltype zapewnia, że ​​ten operator jest używany tylko wtedy, gdy występuje t :: print. W przeciwnym razie próbowałby skompilować treść funkcji i spowodować błąd kompilacji.
QuentinUK
Dodano wersję koncepcyjną
QuentinUK