Bagaimana cara berlebihan membebani operator << untuk ostream?

237

Saya menulis perpustakaan matriks kecil di C ++ untuk operasi matriks. Namun kompiler saya mengeluh, di mana sebelumnya tidak. Kode ini dibiarkan di rak selama 6 bulan dan di antaranya saya memutakhirkan komputer saya dari debian etch ke lenny (g ++ (Debian 4.3.2-1.1) 4.3.2) namun saya memiliki masalah yang sama pada sistem Ubuntu dengan g ++ yang sama .

Inilah bagian yang relevan dari kelas matriks saya:

namespace Math
{
    class Matrix
    {
    public:

        [...]

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

Dan "implementasi":

using namespace Math;

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

    [...]

}

Ini adalah kesalahan yang diberikan oleh kompiler:

matrix.cpp: 459: error: 'std :: ostream & Math :: Matrix :: operator << (std :: ostream &, const Math :: Matrix &)' harus mengambil tepat satu argumen

Saya agak bingung dengan kesalahan ini, tapi sekali lagi C + + saya sudah agak berkarat setelah melakukan banyak Jawa 6 bulan itu. :-)

Matthias van der Vlies
sumber

Jawaban:

127

Anda telah mendeklarasikan fungsi Anda sebagai friend. Itu bukan anggota kelas. Anda harus menghapus Matrix::dari implementasi. friendberarti bahwa fungsi yang ditentukan (yang bukan anggota kelas) dapat mengakses variabel anggota pribadi. Cara Anda menerapkan fungsi seperti metode contoh untuk Matrixkelas yang salah.

Mehrdad Afshari
sumber
7
Dan Anda juga harus mendeklarasikannya di dalam namespace Math (tidak hanya dengan menggunakan namespace Math).
David Rodríguez - dribeas
1
Mengapa operator<<harus ada di namespace Math? Tampaknya itu harus di namespace global. Saya setuju bahwa kompiler saya ingin berada di namespace of Math, tapi itu tidak masuk akal bagi saya.
Mark Lakata
Maaf, tapi saya gagal melihat mengapa kita menggunakan kata kunci teman di sini? Ketika mendeklarasikan operator teman di kelas, sepertinya kita tidak dapat mengimplementasikan dengan Matrix :: operator << (ostream & os, const Matrix & m). Sebaliknya kita hanya perlu menggunakan operator global yang menimpa operator << ostream & os, const Matrix & m) jadi mengapa repot-repot untuk mendeklarasikannya di dalam kelas?
Patrick
139

Hanya memberi tahu Anda tentang satu kemungkinan lain: Saya suka menggunakan definisi teman untuk itu:

namespace Math
{
    class Matrix
    {
    public:

        [...]

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

Fungsi ini akan secara otomatis ditargetkan ke namespace sekitarnya Math(meskipun definisinya muncul dalam lingkup kelas itu) tetapi tidak akan terlihat kecuali jika Anda memanggil operator << dengan objek Matrix yang akan membuat pencarian bergantung argumen menemukan definisi operator. Itu kadang-kadang dapat membantu dengan panggilan ambigu, karena tidak terlihat untuk tipe argumen selain Matrix. Saat menulis definisi, Anda juga dapat merujuk langsung ke nama yang didefinisikan dalam Matrix dan Matrix itu sendiri, tanpa memenuhi syarat nama dengan beberapa awalan yang mungkin panjang dan menyediakan parameter templat seperti Math::Matrix<TypeA, N>.

Johannes Schaub - litb
sumber
77

Untuk menambahkan jawaban Mehrdad,

namespace Math
{
    class Matrix
    {
       public:

       [...]


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

Dalam implementasi Anda

std::ostream& operator<<(std::ostream& stream, 
                     const Math::Matrix& matrix) {
    matrix.print(stream); //assuming you define print for matrix 
    return stream;
 }
kal
sumber
4
Saya tidak mengerti mengapa ini suara turun, ini menjelaskan bahwa Anda dapat mendeklarasikan operator berada di namespace dan bahkan sebagai teman dan bagaimana Anda dapat mendeklarasikan operator.
kal
2
Jawaban Mehrdad tidak memiliki potongan kode jadi saya hanya menambahkan apa yang mungkin berhasil dengan memindahkannya ke luar kelas di namespace itu sendiri.
kal
Saya mengerti maksud Anda, saya hanya melihat cuplikan kedua Anda. Tapi sekarang saya melihat Anda membawa operator keluar dari kelas. Terima kasih untuk sarannya.
Matthias van der Vlies
7
Tidak hanya di luar kelas, tetapi juga didefinisikan dengan baik di dalam namespace Math. Juga memiliki keuntungan tambahan (mungkin bukan untuk Matrix, tetapi dengan kelas lain) bahwa 'cetak' bisa virtual dan dengan demikian pencetakan akan terjadi pada tingkat warisan yang paling diturunkan.
David Rodríguez - dribeas
68

Dengan asumsi bahwa kita sedang berbicara tentang overloading operator <<untuk semua kelas yang berasal dari std::ostreamuntuk menangani Matrixkelas (dan bukan overloading <<untuk Matrixkelas), lebih masuk akal untuk mendeklarasikan fungsi overload di luar namespace Math di header.

Gunakan fungsi teman hanya jika fungsi tidak dapat dicapai melalui antarmuka publik.

Matrix.h

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

Perhatikan bahwa kelebihan operator dinyatakan di luar namespace.

Matrix.cpp

using namespace Math;
using namespace std;

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

Di sisi lain, jika fungsi kelebihan Anda perlu dijadikan teman, maka perlu akses ke anggota pribadi dan yang dilindungi.

Math.h

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

Anda harus menyertakan definisi fungsi dengan blok namespace bukan hanya 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
sumber
38

Di C ++ 14 Anda dapat menggunakan templat berikut untuk mencetak objek yang memiliki T :: print (std :: ostream &) const; anggota.

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

Dalam C ++ 20 Konsep dapat digunakan.

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
sumber
solusi menarik! Satu pertanyaan - di mana operator ini harus dinyatakan, seperti dalam lingkup global? Saya menganggap itu harus terlihat oleh semua jenis yang dapat digunakan untuk templatize itu?
barney
@ Barney Ini bisa di namespace Anda sendiri bersama dengan kelas yang menggunakannya.
QuentinUK
tidak bisakah kamu kembali saja std::ostream&, karena ini adalah tipe pengembaliannya?
Jean-Michaël Celerier
5
@ Jean-MichaëlCelerier Dectype memastikan bahwa operator ini hanya digunakan ketika t :: print hadir. Kalau tidak, ia akan mencoba untuk mengkompilasi fungsi tubuh dan memberikan kesalahan kompilasi.
QuentinUK
Versi konsep ditambahkan, diuji di sini godbolt.org/z/u9fGbK
QuentinUK