Tidak ada == operator yang ditemukan saat membandingkan struct di C ++

96

Membandingkan dua contoh dari struct berikut ini, saya menerima kesalahan:

struct MyStruct1 {
    MyStruct1(const MyStruct2 &_my_struct_2, const int _an_int = -1) :
        my_struct_2(_my_struct_2),
        an_int(_an_int)
    {}

    std::string toString() const;

    MyStruct2 my_struct_2;
    int an_int;
};

Kesalahannya adalah:

kesalahan C2678: binary '==': tidak ditemukan operator yang menggunakan operan kiri jenis 'myproj :: MyStruct1' (atau tidak ada konversi yang dapat diterima)

Mengapa?

Jonathan
sumber

Jawaban:

126

Di C ++, structs tidak memiliki operator perbandingan yang dibuat secara default. Anda perlu menulis sendiri:

bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return /* your comparison code goes here */
}
Anthony Williams
sumber
21
@ Jonathan: Mengapa C ++ tahu bagaimana Anda ingin membandingkan structkesetaraan Anda? Dan jika Anda menginginkan cara yang sederhana, selalu ada memcmpterlalu lama struct Anda tidak berisi pointer.
Xeo
12
@Xeo: memcmpgagal dengan anggota non-POD (suka std::string) dan struktur berlapis.
fredoverflow
16
@Jonathan Bahasa "modern" yang saya tahu memang menyediakan ==operator --- dengan semantik yang hampir tidak pernah diinginkan. (Dan mereka tidak menyediakan sarana untuk menimpanya, jadi Anda akhirnya harus menggunakan fungsi anggota). Bahasa "modern" yang saya tahu juga tidak memberikan semantik nilai, jadi Anda terpaksa menggunakan pointer, meskipun tidak sesuai.
James Kanze
4
Kasus @Jonathan memang berbeda-beda, bahkan dalam program tertentu. Untuk objek entitas, solusi yang disediakan oleh Java bekerja dengan sangat baik (dan tentu saja, Anda dapat melakukan hal yang persis sama di C ++ --- bahkan C ++ idiomatik untuk objek entitas). Pertanyaannya adalah apa yang harus dilakukan terhadap objek nilai. C ++ menyediakan default operator=(meskipun sering melakukan hal yang salah), karena alasan kompatibilitas C. Kompatibilitas C tidak membutuhkan operator==. Secara global, saya lebih suka apa yang dilakukan C ++ daripada apa yang dilakukan Java. (Saya tidak tahu C #, jadi mungkin itu lebih baik.)
James Kanze
9
Setidaknya itu harus mungkin = default!
pengguna362515
94

C ++ 20 diperkenalkan perbandingan default, alias "angkasa"operator<=> , yang memungkinkan Anda untuk permintaan compiler yang dihasilkan </ <=/ ==/ !=/ >=/ dan / atau >operator dengan (?) Pelaksanaan yang jelas / naif ...

auto operator<=>(const MyClass&) const = default;

... tetapi Anda dapat menyesuaikannya untuk situasi yang lebih rumit (dibahas di bawah). Lihat di sini untuk proposal bahasa, yang berisi pembenaran dan diskusi. Jawaban ini tetap relevan untuk C ++ 17 dan sebelumnya, dan untuk wawasan tentang kapan Anda harus menyesuaikan penerapan operator<=>....

Tampaknya agak tidak membantu C ++ untuk tidak menstandarkan ini sebelumnya, tetapi seringkali struct / class memiliki beberapa anggota data untuk dikecualikan dari perbandingan (misalnya penghitung, hasil yang di-cache, kapasitas penampung, keberhasilan operasi terakhir / kode kesalahan, kursor), seperti serta keputusan yang harus diambil tentang banyak hal termasuk namun tidak terbatas pada:

  • bidang mana yang akan dibandingkan pertama kali, misalnya membandingkan anggota tertentu intmungkin menghilangkan 99% objek yang tidak sama dengan sangat cepat, sementara map<string,string>anggota mungkin sering memiliki entri yang identik dan relatif mahal untuk dibandingkan - jika nilai dimuat pada waktu proses, pemrogram mungkin memiliki wawasan kompiler tidak mungkin
  • dalam membandingkan string: sensitivitas huruf, kesetaraan spasi dan pemisah, keluar dari konvensi ...
  • presisi saat membandingkan float / double
  • apakah nilai floating point NaN harus dianggap sama
  • membandingkan pointer atau point-to-data (dan jika yang terakhir, bagaimana mengetahui bagaimana pointer ke array dan berapa banyak objek / byte yang perlu dibandingkan)
  • apakah urutan penting saat membandingkan wadah yang tidak diurutkan (misalnya vector, list), dan jika demikian apakah boleh untuk mengurutkannya di tempat sebelum membandingkan vs. menggunakan memori ekstra untuk mengurutkan temporer setiap kali perbandingan dilakukan
  • berapa banyak elemen array yang saat ini memiliki nilai valid yang harus dibandingkan (apakah ada ukuran di suatu tempat atau sentinel?)
  • anggota yang mana yang unionakan dibandingkan
  • normalisasi: misalnya, jenis tanggal memungkinkan di luar rentang hari-dari-bulan atau bulan-tahun, atau objek rasional / pecahan mungkin memiliki 6/8 sementara yang lain memiliki 3/4, yang karena alasan kinerja mereka mengoreksi malas dengan langkah normalisasi terpisah; Anda mungkin harus memutuskan apakah akan memicu normalisasi sebelum melakukan perbandingan
  • apa yang harus dilakukan jika petunjuk lemah tidak valid
  • bagaimana menangani anggota dan pangkalan yang tidak menerapkan operator==dirinya sendiri (tetapi mungkin memiliki compare()atau operator<atau str()atau getter ...)
  • kunci apa yang harus diambil saat membaca / membandingkan data yang mungkin ingin diperbarui oleh utas lain

Jadi, agak menyenangkan untuk memiliki kesalahan sampai Anda secara eksplisit memikirkan tentang apa arti perbandingan untuk struktur spesifik Anda, daripada membiarkannya terkompilasi tetapi tidak memberi Anda hasil yang berarti pada waktu proses .

Semua yang dikatakan, akan lebih baik jika C ++ membiarkan Anda mengatakan bool operator==() const = default;ketika Anda memutuskan ==tes anggota-demi-anggota yang "naif" tidak apa - apa. Sama untuk !=. Beberapa anggota diberikan / basa, "default" <, <=, >, dan >=implementasi tampak putus asa meskipun - Cascading atas dasar urutan deklarasi ini mungkin tapi sangat tidak mungkin apa yang ingin, mengingat bertentangan imperatif untuk anggota memesan (basa menjadi tentu sebelum anggota, pengelompokan oleh aksesibilitas, konstruksi / penghancuran sebelum penggunaan bergantung). Agar lebih bermanfaat secara luas, C ++ akan membutuhkan sistem anotasi anggota / basis data baru untuk memandu pilihan - itu akan menjadi hal yang hebat untuk dimiliki dalam Standar, idealnya digabungkan dengan pembuatan kode yang ditentukan pengguna berbasis AST ... Saya harap Itu'

Implementasi khas dari operator kesetaraan

Implementasi yang masuk akal

Ini kemungkinan bahwa implementasi yang wajar dan efisien akan menjadi:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.my_struct2 == rhs.my_struct2 &&
           lhs.an_int     == rhs.an_int;
}

Catatan bahwa ini membutuhkan operator==untuk MyStruct2juga.

Implikasi dari implementasi ini, dan alternatifnya, dibahas di bawah judul Diskusi spesifik MyStruct1 Anda di bawah ini.

Pendekatan yang konsisten untuk ==, <,> <= dll

Sangat mudah untuk memanfaatkan std::tupleoperator perbandingan untuk membandingkan instance kelas Anda sendiri - cukup gunakan std::tieuntuk membuat tupel referensi ke bidang dalam urutan perbandingan yang diinginkan. Menggeneralisasikan contoh saya dari sini :

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) ==
           std::tie(rhs.my_struct2, rhs.an_int);
}

inline bool operator<(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) <
           std::tie(rhs.my_struct2, rhs.an_int);
}

// ...etc...

Saat Anda "memiliki" (yaitu dapat mengedit, faktor dengan lib perusahaan dan pihak ketiga) kelas yang ingin Anda bandingkan, dan terutama dengan kesiapan C ++ 14 untuk menyimpulkan jenis kembalian fungsi dari returnpernyataan, sering kali lebih baik menambahkan " ikat "fungsi anggota ke kelas yang ingin Anda bandingkan:

auto tie() const { return std::tie(my_struct1, an_int); }

Kemudian perbandingan di atas disederhanakan menjadi:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.tie() == rhs.tie();
}

Jika Anda menginginkan kumpulan operator perbandingan yang lebih lengkap, saya sarankan operator tingkatkan (cari less_than_comparable). Jika tidak cocok karena alasan tertentu, Anda mungkin menyukai atau tidak menyukai gagasan makro dukungan (online) :

#define TIED_OP(STRUCT, OP, GET_FIELDS) \
    inline bool operator OP(const STRUCT& lhs, const STRUCT& rhs) \
    { \
        return std::tie(GET_FIELDS(lhs)) OP std::tie(GET_FIELDS(rhs)); \
    }

#define TIED_COMPARISONS(STRUCT, GET_FIELDS) \
    TIED_OP(STRUCT, ==, GET_FIELDS) \
    TIED_OP(STRUCT, !=, GET_FIELDS) \
    TIED_OP(STRUCT, <, GET_FIELDS) \
    TIED_OP(STRUCT, <=, GET_FIELDS) \
    TIED_OP(STRUCT, >=, GET_FIELDS) \
    TIED_OP(STRUCT, >, GET_FIELDS)

... yang kemudian dapat digunakan a la ...

#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)

(Versi dasi anggota C ++ 14 di sini )

Diskusi spesifik MyStruct1 Anda

Ada implikasi pada pilihan untuk memberikan status bebas versus anggota operator==()...

Implementasi berdiri bebas

Anda harus membuat keputusan yang menarik. Karena kelas Anda dapat secara implisit dibangun dari a MyStruct2, fungsi berdiri bebas / non-anggota bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)akan mendukung ...

my_MyStruct2 == my_MyStruct1

... dengan terlebih dahulu membuat temporary MyStruct1from my_myStruct2, lalu melakukan perbandingan. Ini pasti akan meninggalkan MyStruct1::an_intset ke nilai parameter default konstruktor -1. Tergantung pada apakah Anda termasuk an_intperbandingan dalam pelaksanaan Anda operator==, sebuah MyStruct1kekuatan atau mungkin tidak membandingkan sama dengan MyStruct2itu sendiri membandingkan sama dengan MyStruct1's my_struct_2anggota! Lebih lanjut, membuat sementara MyStruct1bisa menjadi operasi yang sangat tidak efisien, karena melibatkan penyalinan my_struct2anggota yang ada ke sementara, hanya untuk membuangnya setelah perbandingan. (Tentu saja, Anda dapat mencegah konstruksi implisit MyStruct1s ini untuk perbandingan dengan membuat konstruktor tersebut explicitatau menghapus nilai default untuk an_int.)

Implementasi anggota

Jika Anda ingin menghindari konstruksi implisit MyStruct1dari a MyStruct2, jadikan operator perbandingan sebagai fungsi anggota:

struct MyStruct1
{
    ...
    bool operator==(const MyStruct1& rhs) const
    {
        return tie() == rhs.tie(); // or another approach as above
    }
};

Perhatikan constkata kunci - hanya diperlukan untuk implementasi anggota - menyarankan compiler bahwa membandingkan objek tidak mengubahnya, jadi dapat diizinkan pada constobjek.

Membandingkan representasi yang terlihat

Terkadang cara termudah untuk mendapatkan jenis perbandingan yang Anda inginkan adalah ...

    return lhs.to_string() == rhs.to_string();

... yang seringkali juga sangat mahal - semua stringitu dibuat dengan susah payah hanya untuk dibuang! Untuk tipe dengan nilai floating point, membandingkan representasi yang terlihat berarti jumlah digit yang ditampilkan menentukan toleransi di mana nilai yang hampir sama diperlakukan sama selama perbandingan.

Tony Delroy
sumber
Sebenarnya untuk operator pembanding <,>, <=,> = seharusnya hanya diperlukan untuk mengimplementasikan <. Sisanya mengikuti, dan tidak ada cara yang berarti untuk mengimplementasikannya yang berarti sesuatu yang berbeda dari implementasi yang dapat dihasilkan secara otomatis. Anehnya Anda harus menerapkan semuanya sendiri.
André
@ André: lebih sering secara manual tertulis int cmp(x, y)atau comparefungsi mengembalikan nilai negatif untuk x < y, 0 untuk kesetaraan dan nilai positif bagi x > ydigunakan sebagai dasar untuk <, >, <=, >=, ==, dan !=; sangat mudah menggunakan CRTP untuk memasukkan semua operator tersebut ke dalam kelas. Saya yakin saya telah memposting implementasi dalam jawaban lama, tetapi tidak dapat menemukannya dengan cepat.
Tony Delroy
@TonyD Tentu Anda bisa melakukan itu, tapi itu mudah diimplementasikan >, <=dan >=dalam hal <. Anda juga bisa mengimplementasikan ==dan dengan !=cara itu, tapi itu biasanya bukan implementasi yang sangat efisien. Akan lebih baik jika tidak ada CRTP atau trik lain yang diperlukan untuk semua ini, tetapi standar hanya akan mengamanatkan pembuatan otomatis operator ini jika tidak secara eksplisit ditentukan oleh pengguna dan <ditentukan.
André
@ André: Itu karena ==dan !=mungkin tidak diekspresikan secara efisien dengan <menggunakan bandingkan untuk semuanya adalah umum. "Akan lebih baik jika tidak ada CRTP atau trik lainnya akan diperlukan" - mungkin, tapi kemudian CRTP dapat dengan mudah digunakan untuk menghasilkan banyak operator lain (misalnya bitwise |, &, ^dari |=, &=dan ^=; + - * / %dari bentuk-bentuk tugas mereka; biner -dari negasi unary dan +) - begitu banyak variasi yang berpotensi berguna pada tema ini sehingga hanya menyediakan fitur bahasa untuk satu bagian yang cukup acak yang tidak terlalu elegan.
Tony Delroy
Maukah Anda menambahkan implementasi yang masuk akal versi yang digunakan std::tieuntuk melakukan perbandingan beberapa anggota?
NathanOliver
17

Anda perlu secara eksplisit menentukan operator ==untuk MyStruct1.

struct MyStruct1 {
  bool operator == (const MyStruct1 &rhs) const
  { /* your logic for comparision between "*this" and "rhs" */ }
};

Sekarang perbandingan == legal untuk 2 objek tersebut.

iammilind
sumber
11

Mulai di C ++ 20, itu harus mungkin untuk menambahkan set lengkap operator perbandingan default ( ==, <=, dll) untuk kelas dengan mendeklarasikan bawaan operator perbandingan tiga arah ( "angkasa" operator), seperti ini:

struct Point {
    int x;
    int y;
    auto operator<=>(const Point&) const = default;
};

Dengan kompiler C ++ 20 yang sesuai, menambahkan baris tersebut ke MyStruct1 dan MyStruct2 mungkin cukup untuk memungkinkan perbandingan kesetaraan, dengan asumsi definisi MyStruct2 kompatibel.

Joe Lee
sumber
2

Perbandingan tidak berfungsi pada struct di C atau C ++. Bandingkan menurut bidang.

Rafe Kettler
sumber
2

Secara default struct tidak memiliki ==operator. Anda harus menulis implementasi Anda sendiri:

bool MyStruct1::operator==(const MyStruct1 &other) const {
    ...  // Compare the values, and return a bool result.
  }
Jonathan
sumber
0

Di luar kotak, operator == hanya berfungsi untuk primitif. Agar kode Anda berfungsi, Anda perlu membebani operator == untuk struct Anda.

Babak Naffas
sumber
0

Karena Anda tidak menulis operator perbandingan untuk struct Anda. Kompilator tidak membuatnya untuk Anda, jadi jika Anda ingin perbandingan, Anda harus menulisnya sendiri.


sumber