Haruskah operator << diimplementasikan sebagai teman atau sebagai fungsi anggota?

129

Pada dasarnya itulah pertanyaannya, apakah ada cara yang "benar" untuk menerapkan operator<<? Membaca ini saya dapat melihat bahwa sesuatu seperti:

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

lebih disukai daripada sesuatu seperti

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

Tapi saya tidak bisa mengerti mengapa saya harus menggunakan salah satu.

Kasus pribadi saya adalah:

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

Tapi saya mungkin bisa melakukan:

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

Apa alasan saya harus mendasarkan keputusan ini?

Catatan :

 Paragraph::to_str = (return paragraph) 

dimana paragraf adalah string.

Federico Builes
sumber
4
BTW Anda mungkin harus menambahkan const ke tanda tangan fungsi anggota
Motti
4
Mengapa mengembalikan bool dari operator <<? Apakah Anda menggunakannya sebagai operator streaming atau sebagai overload dari bitwise shift?
Martin York

Jawaban:

120

Masalahnya di sini adalah interpretasi Anda terhadap artikel yang Anda tautkan .

Persamaan

Artikel ini tentang seseorang yang mengalami masalah dalam mendefinisikan operator hubungan bool dengan benar.

Operator:

  • Kesetaraan == dan! =
  • Hubungan <> <=> =

Operator ini harus mengembalikan bool karena mereka membandingkan dua objek dengan tipe yang sama. Biasanya paling mudah untuk mendefinisikan operator ini sebagai bagian dari kelas. Ini karena sebuah kelas secara otomatis menjadi teman dari dirinya sendiri sehingga objek berjenis Paragraph dapat saling memeriksa (bahkan satu sama lain anggota pribadi).

Ada argumen untuk membuat fungsi berdiri bebas ini karena ini memungkinkan konversi otomatis mengonversi kedua sisi jika mereka bukan jenis yang sama, sementara fungsi anggota hanya memungkinkan rhs dikonversi otomatis. Saya menemukan ini argumen pria kertas karena Anda tidak benar-benar ingin konversi otomatis terjadi di tempat pertama (biasanya). Tetapi jika ini adalah sesuatu yang Anda inginkan (saya tidak merekomendasikannya) maka membuat komparator berdiri bebas bisa menguntungkan.

Streaming

Operator aliran:

  • operator << keluaran
  • operator >> masukan

Jika Anda menggunakan ini sebagai operator aliran (bukan pergeseran biner), parameter pertama adalah aliran. Karena Anda tidak memiliki akses ke objek aliran (bukan milik Anda untuk dimodifikasi) ini tidak dapat menjadi operator anggota, mereka harus berada di luar kelas. Oleh karena itu, mereka harus menjadi teman sekelas atau memiliki akses ke metode publik yang akan melakukan streaming untuk Anda.

Ini juga tradisional untuk objek ini untuk mengembalikan referensi ke objek aliran sehingga Anda bisa merantai operasi aliran bersama-sama.

#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;
}
Martin York
sumber
19
Mengapa operator<< private:?
Matt Clarkson
47
@ MattClarkson: Ini bukan. Ini adalah deklarasi fungsi teman sehingga bukan bagian dari kelas dan dengan demikian tidak terpengaruh oleh penentu akses. Saya biasanya meletakkan deklarasi fungsi teman di sebelah data yang mereka akses.
Martin York
12
Mengapa perlu fungsi yang bersahabat, jika Anda menggunakan fungsi publik untuk mengakses data? Maaf, jika pertanyaannya bodoh.
Semyon Danilov
4
@SemyonDanilov: Mengapa Anda merusak enkapsulasi dan menambahkan getter! freiendadalah cara untuk memperluas antarmuka publik tanpa merusak enkapsulasi. Baca programmers.stackexchange.com/a/99595/12917
Martin York
3
@LokiAstari Tapi tentunya itu argumen untuk menghapus to_str atau membuatnya pribadi. Seperti berdiri, operator streaming tidak harus menjadi teman, karena hanya menggunakan fungsi publik.
deworde
53

Anda tidak dapat melakukannya sebagai fungsi anggota, karena thisparameter implisit adalah sisi kiri <<-operator. (Oleh karena itu, Anda perlu menambahkannya sebagai fungsi anggota ke ostream-kelas. Tidak bagus :)

Bisakah Anda melakukannya sebagai fungsi gratis tanpa frienditu? Itulah yang saya suka, karena menjelaskan bahwa ini adalah integrasi dengan ostream, dan bukan fungsionalitas inti kelas Anda.

Magnus Hoff
sumber
1
"bukan fungsi inti kelas Anda." Itulah yang dimaksud dengan "teman". Jika itu adalah fungsionalitas inti, itu akan ada di kelas, bukan teman.
xaxxon
1
@xaxxon Saya pikir kalimat pertama saya menjelaskan mengapa tidak mungkin dalam kasus ini menambahkan fungsi sebagai fungsi anggota. Sebuah friendfungsi memiliki hak yang sama sebagai fungsi anggota ( ini adalah apa friendartinya), sehingga pengguna kelas, saya harus bertanya-tanya mengapa hal itu akan membutuhkan. Ini adalah perbedaan yang saya coba buat dengan kata-kata "fungsionalitas inti".
Magnus Hoff
32

Jika memungkinkan, sebagai fungsi non-anggota dan non-teman.

Seperti yang dijelaskan oleh Herb Sutter dan Scott Meyers, lebih memilih fungsi non-teman bukan anggota daripada fungsi anggota, untuk membantu meningkatkan enkapsulasi.

Dalam beberapa kasus, seperti streaming C ++, Anda tidak memiliki pilihan dan harus menggunakan fungsi non-anggota.

Namun tetap saja, itu tidak berarti Anda harus menjadikan fungsi-fungsi ini sebagai teman kelas Anda: Fungsi-fungsi ini masih dapat mengakses kelas Anda melalui pengakses kelas Anda. Jika Anda berhasil menyusun fungsi tersebut dengan cara ini, maka Anda menang.

Tentang prototipe operator << dan >>

Saya yakin contoh yang Anda berikan dalam pertanyaan Anda salah. Sebagai contoh;

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

Saya bahkan tidak bisa mulai memikirkan bagaimana metode ini bisa bekerja dalam aliran.

Berikut adalah dua cara untuk mengimplementasikan operator << dan >>.

Katakanlah Anda ingin menggunakan objek seperti aliran tipe T.

Dan Anda ingin mengekstrak / menyisipkan dari / ke dalam T data yang relevan dari objek Anda berjenis Paragraph.

Operator generik << dan >> prototipe fungsi

Yang pertama sebagai fungsi:

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

Prototipe metode << dan >> operator generik

Yang kedua sebagai metode:

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

Perhatikan bahwa untuk menggunakan notasi ini, Anda harus memperluas deklarasi kelas T. Untuk objek STL, ini tidak dimungkinkan (Anda tidak seharusnya memodifikasinya ...).

Dan bagaimana jika T adalah aliran C ++?

Berikut adalah prototipe dari operator << dan >> yang sama untuk aliran C ++.

Untuk basic_istream dan basic_ostream generik

Perhatikan bahwa ini adalah kasus streaming, karena Anda tidak dapat mengubah aliran C ++, Anda harus mengimplementasikan fungsinya. Artinya seperti:

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

Untuk char istream dan ostream

Kode berikut hanya akan berfungsi untuk aliran berbasis karakter.

// 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 berkomentar tentang fakta bahwa kode berbasis char hanyalah "spesialisasi" dari kode generik di atasnya. Tentu saja, Rhys benar: Saya tidak merekomendasikan penggunaan contoh berbasis karakter. Ini hanya diberikan di sini karena lebih mudah dibaca. Karena ini hanya dapat dijalankan jika Anda hanya bekerja dengan aliran berbasis char, Anda harus menghindarinya pada platform di mana kode wchar_t umum (misalnya pada Windows).

Semoga ini bisa membantu.

paercebal.dll
sumber
Bukankah kode template basic_istream dan basic_ostream Anda sudah mencakup versi std :: ostream- dan std :: istream-spesifik karena dua yang terakhir hanyalah contoh dari yang sebelumnya menggunakan chars?
Rhys Ulerich
@Rhys Ulerich: Tentu saja. Saya hanya menggunakan versi generik dan templated, jika hanya karena pada Windows, Anda harus berurusan dengan kode char dan wchar_t. Satu-satunya kelebihan versi kedua adalah tampil lebih sederhana dari yang pertama. Saya akan menjelaskan posting saya tentang itu.
paercebal
10

Ini harus diimplementasikan sebagai fungsi non-teman gratis, terutama jika, seperti kebanyakan hal hari ini, keluarannya terutama digunakan untuk diagnostik dan logging. Tambahkan pengakses const untuk semua hal yang perlu dimasukkan ke dalam keluaran, lalu minta keluaran tersebut untuk memanggilnya dan melakukan pemformatan.

Saya sebenarnya telah mengumpulkan semua fungsi gratis keluaran ostream ini di header "ostreamhelpers" dan file implementasi, itu membuat fungsi sekunder itu jauh dari tujuan sebenarnya dari kelas.

XPav
sumber
7

Tanda tangannya:

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

Tampaknya agak mencurigakan, ini tidak sesuai dengan streamkonvensi atau konvensi bitwise sehingga terlihat seperti kasus penyalahgunaan kelebihan beban operator, operator <harus kembali booltetapi operator <<mungkin harus mengembalikan sesuatu yang lain.

Jika Anda bermaksud begitu, katakan:

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

Kemudian karena Anda tidak dapat menambahkan fungsi ostreamdengan kebutuhan, fungsi tersebut harus merupakan fungsi gratis, apakah itu friendtergantung atau tidak pada apa yang harus diakses (jika tidak perlu mengakses anggota pribadi atau dilindungi, tidak perlu membuatnya. teman).

Motti
sumber
Perlu disebutkan akses untuk memodifikasi ostreamakan diperlukan saat menggunakan ostream.operator<<(obj&)pemesanan; karenanya fungsi bebas. Jika tidak, tipe pengguna perlu tipe uap untuk mengakomodasi akses.
wulfgarpro
2

Demi penyelesaian, saya ingin menambahkan bahwa Anda memang dapat membuat operator ostream& operator << (ostream& os)di dalam kelas dan dapat berfungsi. Dari apa yang saya tahu, bukan ide yang baik untuk menggunakannya, karena sangat berbelit-belit dan tidak intuitif.

Mari kita asumsikan kita memiliki kode ini:

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

Jadi untuk menyimpulkannya - Anda bisa melakukannya, tetapi kemungkinan besar Anda tidak boleh :)

ashrasmun
sumber
0

operator teman = hak yang sama sebagai kelas

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

operator<< diimplementasikan sebagai fungsi teman:

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

} 

OUTPUT:
100 Halo
100 Halo

Ini bisa menjadi fungsi teman hanya karena objek ada di sisi kanan operator<<dan argumen coutdi sisi kiri. Jadi ini tidak bisa menjadi fungsi anggota kelas, itu hanya bisa menjadi fungsi teman.

Rohit Vipin Mathews
sumber
Saya rasa tidak ada cara untuk menulis ini sebagai anggota funtion !!
Rohit Vipin Mathews
Mengapa semuanya berani. Biarkan saya menghapus ini.
Sebastian Mach