Mengapa ketidaksetaraan diuji sebagai (! (A == b)) di banyak kode pustaka standar C ++?


142

Ini adalah kode dari removekode pustaka standar C ++ . Mengapa ketimpangan diuji sebagai if (!(*first == val))ganti if (*first != val)?

 template <class ForwardIterator, class T>
      ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val)
 {
     ForwardIterator result = first;
     while (first!=last) {
         if (!(*first == val)) {
             *result = *first;
             ++result;
         }
         ++first;
     }
     return result;
 }

2
@BeyelerStudios mungkin benar. Itu juga umum ketika menerapkan operator!=. Cukup gunakan operator==implementasinya:bool operator!=(const Foo& other) { return !(*this == other); }
simon

1
sebenarnya saya benar pernyataan saya: referensi menyebutkan menghapus semua elemen yang sama dengan nilai sehingga operator==agak diharapkan untuk digunakan di sini ...
BeyelerStudios

Oh, dan harus ada juga constdalam contoh di komentar saya sebelumnya, tetapi Anda mengerti maksudnya. (Terlambat untuk mengeditnya)
simon

Alasan untuk ini terkait dengan pertanyaan lain (yang pada dasarnya dapat dijawab dengan "Tidak, belum tentu"), dan konsep menjadi EqualityComparableyang Hurkyl disebutkan dalam jawabannya .
Marco13

Jawaban:


144

Karena ini berarti satu-satunya persyaratan pada T adalah untuk mengimplementasikan operator==. Anda dapat meminta T untuk memiliki operator!=tetapi gagasan umum di sini adalah bahwa Anda harus memberi beban sesedikit mungkin kepada pengguna template dan template lain memang perlu operator==.


13
template <class T> operator bool inline! = <T a, T b> {return! (a == b); }
Joshua

8
Apakah akan ada skenario di mana kompiler tidak akan dapat menukar semua instance =! ke! (==)? Mengapa ini belum menjadi tindakan default untuk diambil oleh kompiler?
Aidan Gomez

20
@AidanGomez Untuk lebih baik atau lebih buruk Anda dapat membebani operator untuk melakukan apa pun yang Anda inginkan. Itu tidak harus masuk akal atau konsisten.
Neil Kirk

10
x != ytidak didefinisikan sama dengan !(x == y). Bagaimana jika operator ini mengembalikan pohon parse dari DSL yang disematkan?
Brice M. Dempsey

7
@ Joshua Itu rusak parah jika mencoba menggunakan SFINAE untuk mendeteksi apakah !=didukung (salah akan mengembalikan true - bahkan jika operator==tidak didukung!). Saya juga khawatir itu akan menyebabkan beberapa kegunaan !=menjadi ambigu.

36

Sebagian besar fungsi dalam STL hanya bekerja dengan operator<atau operator==. Ini mengharuskan pengguna hanya untuk mengimplementasikan dua operator ini (atau kadang-kadang setidaknya satu dari mereka). Misalnya std::setmenggunakan operator<(lebih tepatnya std::lessyang memanggil operator<secara default) dan tidak operator>mengelola pemesanan. The removetemplate contoh Anda adalah kasus serupa - hanya menggunakan operator==dan tidak operator!=sehingga operator!=tidak perlu didefinisikan.


2
Fungsi tidak digunakan operator<secara langsung melainkan digunakan std::less, yang pada gilirannya menjadi default operator<.
Christian Hackl

1
Sebenarnya, tampaknya fungsi algoritma standar, tidak seperti misalnya std::set, memang digunakan operator<secara langsung. Aneh ...
Christian Hackl

1
Fungsi-fungsi ini tidak digunakan std::equal_to, mereka menggunakan operator==sebagaimana dicatat dalam pertanyaan. Situasi dengan std::lessmirip. Yah, mungkin std::setbukan contoh terbaik.
Lukáš Bednařík

2
@ChristianHackl, std::equal_todan std::lessdigunakan sebagai parameter templat default tempat pembanding diambil sebagai parameter. operator==dan operator<digunakan secara langsung di mana jenis ini diperlukan untuk memenuhi kesetaraan yang sebanding dan urutan lemah masing-masing, misalnya iterator dan iterator akses acak.
Jan Hudec

28

Ini adalah kode dari pustaka penghapusan kode standar C ++.

Salah. Ini bukan yang C ++ library standar removekode. Ini adalah salah satu implementasi internal dari removefungsi pustaka standar C ++ . Standar C ++ tidak menentukan kode aktual; itu menetapkan prototipe fungsi dan perilaku yang diperlukan.

Dengan kata lain: Dari sudut pandang bahasa yang ketat, kode yang Anda lihat tidak ada . Mungkin dari beberapa file header yang datang dengan implementasi perpustakaan standar compiler Anda. Perhatikan bahwa standar C ++ bahkan tidak mengharuskan file header itu ada. File hanyalah cara yang nyaman bagi implementator kompiler untuk memenuhi persyaratan untuk jalur seperti #include <algorithm>(yaitu membuat std::removedan fungsi lainnya tersedia).

Mengapa ketimpangan diuji sebagai if (!(*first == val))ganti if (*first != val)?

Karena hanya operator==diperlukan oleh fungsi.

Ketika berbicara tentang overloading operator untuk tipe khusus, bahasa ini memungkinkan Anda melakukan semua jenis hal aneh. Anda bisa membuat kelas yang memiliki kelebihan operator==tetapi tidak kelebihan operator!=. Atau bahkan lebih buruk: Anda bisa kelebihan beban operator!=tetapi membuatnya melakukan hal-hal yang sama sekali tidak terkait.

Pertimbangkan contoh ini:

#include <algorithm>
#include <vector>

struct Example
{
    int i;

    Example() : i(0) {}

    bool operator==(Example const& other) const
    {
        return i == other.i;
    }

    bool operator!=(Example const& other) const
    {
        return i == 5; // weird, but nothing stops you
                       // from doing so
    }

};

int main()
{
  std::vector<Example> v(10);
  // ...
  auto it = std::remove(v.begin(), v.end(), Example());
  // ...
}

Jika std::removedigunakan operator!=, maka hasilnya akan sangat berbeda.


1
Hal lain yang perlu dipertimbangkan adalah bahwa mungkin bagi keduanya a==bdan a!=buntuk kembali salah. Meskipun mungkin tidak selalu jelas apakah situasi seperti itu akan lebih bermakna dianggap sebagai "sama" atau "tidak sama", suatu fungsi yang mendefinisikan kesetaraan semata-mata dalam hal operator "==" harus menganggapnya sebagai "tidak sama dengan" ", terlepas dari perilaku mana yang lebih masuk akal [jika saya memiliki pemabuk saya, semua jenis diharapkan membuat operator menghasilkan-boolean" == "dan"! = "operator berperilaku secara konsisten, tetapi fakta bahwa mandat IEEE-754 melanggar kesetaraan operator akan membuat harapan seperti itu sulit].
supercat

18
"Dari sudut pandang bahasa yang ketat, kode yang Anda lihat tidak ada." - ketika sudut pandang mengatakan sesuatu tidak ada, tetapi Anda benar-benar melihat hal itu, maka sudut pandang itu salah. Bahkan standar tidak mengatakan kode itu tidak ada, itu hanya tidak mengatakan itu memang ada :-)
Steve Jessop

@SteveJessop: Anda dapat mengganti ekspresi "dari sudut pandang bahasa yang ketat" dengan sesuatu seperti "ketat, pada tingkat bahasa". Intinya adalah bahwa kode yang diposting oleh OP bukan urusan standar bahasa.
Christian Hackl

2
@supercat: IEEE-754 membuat ==dan !=berperilaku secara konsisten, meskipun saya selalu berpikir bahwa keenam hubungan harus dievaluasi falseketika setidaknya satu operan NaN.
Ben Voigt

@ BenVoigt: Ah, benar juga. Itu membuat dua operator berperilaku dengan cara yang sama rusak sehingga mereka konsisten satu sama lain, tetapi masih berhasil melanggar semua aksioma normal lainnya yang terkait dengan kesetaraan (misalnya mereka tidak menjunjung a == a, maupun jaminan bahwa operasi dilakukan pada nilai yang sama akan menghasilkan hasil yang sama).
supercat

15

Beberapa jawaban bagus di sini. Saya hanya ingin menambahkan sedikit catatan.

Seperti semua perpustakaan yang baik, perpustakaan standar dirancang dengan (setidaknya) dua prinsip yang sangat penting dalam pikiran:

  1. Berikan paling sedikit tanggung jawab pada pengguna perpustakaan Anda yang bisa Anda hindari. Sebagian dari ini berkaitan dengan memberi mereka jumlah pekerjaan paling sedikit untuk dilakukan saat menggunakan antarmuka Anda. (Seperti mendefinisikan beberapa operator karena Anda bisa lolos). Bagian lain dari itu ada hubungannya dengan tidak mengejutkan mereka atau mengharuskan mereka untuk memeriksa kode kesalahan (jadi menjaga antarmuka konsisten dan membuang pengecualian dari <stdexcept>saat ada masalah).

  2. Hilangkan semua redundansi logis . Semua perbandingan dapat disimpulkan hanya dari operator<, jadi mengapa menuntut pengguna untuk mendefinisikan yang lain? misalnya:

    (a> b) setara dengan (b <a)

    (a> = b) setara dengan! (a <b)

    (a == b) setara dengan! ((a <b) || (b <a))

    dan seterusnya.

    Tentu saja pada catatan ini, orang mungkin bertanya mengapa unordered_mapmemerlukan operator==(setidaknya secara default) daripada operator<. Jawabannya adalah bahwa dalam tabel hash satu-satunya perbandingan yang pernah kita butuhkan adalah satu untuk kesetaraan. Dengan demikian lebih konsisten secara logis (yaitu lebih masuk akal bagi pengguna perpustakaan) untuk meminta mereka mendefinisikan operator kesetaraan. Membutuhkan operator<akan membingungkan karena tidak segera jelas mengapa Anda membutuhkannya.


10
Mengenai poin kedua Anda: Ada jenis yang secara logis dapat dibandingkan untuk kesetaraan bahkan jika tidak ada tatanan logis ada, atau yang membuat tatanan akan sangat buatan. Misalnya, merah adalah merah dan merah bukan hijau, tetapi apakah merah pada dasarnya kurang dari hijau?
Christian Hackl

1
Sangat setuju. Seseorang tidak akan menyimpan barang-barang ini dalam wadah yang dipesan karena tidak ada urutan logis. Mereka mungkin lebih cocok disimpan dalam wadah tidak teratur yang membutuhkan operator==(dan hash).
Richard Hodges

3. Overload setidaknya operator standar mungkin. Ini adalah alasan lain yang mereka terapkan !(a==b). Karena overloading yang tidak direfleksikan dari operator dapat dengan mudah menyebabkan program C ++ menjadi kacau (ditambah, membuat programmer menjadi gila karena debug kode-nya mungkin menjadi misi yang mustahil, karena menemukan penyebab bug tertentu menyerupai pengembaraan).
syntaxerror

!((a < b) || (b < a))menggunakan satu operator bool lebih sedikit, jadi mungkin lebih cepat
Filip Haglund

1
Pada kenyataannya mereka tidak. Dalam bahasa assembly semua perbandingan diimplementasikan sebagai pengurangan diikuti dengan pengujian carry dan nol bit pada register flag. Yang lainnya hanyalah gula sintaksis.
Richard Hodges

8

The EqualityComparableKonsep hanya mengharuskan operator==didefinisikan.

Akibatnya, fungsi apa pun yang mengaku bekerja dengan jenis yang memuaskan EqualityComparable tidak bisa bergantung pada keberadaan operator!=objek untuk jenis itu. (kecuali ada persyaratan tambahan yang menyiratkan adanya operator!=).


1

Pendekatan yang paling menjanjikan adalah menemukan metode untuk menentukan apakah operator == dapat dipanggil untuk jenis tertentu, dan kemudian mendukungnya hanya jika tersedia; dalam situasi lain, pengecualian akan dilempar. Namun, hingga saat ini tidak ada cara yang diketahui untuk mendeteksi apakah ekspresi operator yang sewenang-wenang f ==g didefinisikan dengan tepat. Solusi terbaik yang dikenal memiliki kualitas yang tidak diinginkan berikut:

  • Gagal pada waktu kompilasi untuk objek di mana operator == tidak dapat diakses (misalnya, karena bersifat pribadi).
  • Gagal pada waktu kompilasi jika memanggil operator == ambigu.
  • Tampaknya benar jika operator == pernyataan sudah benar, meskipun operator == mungkin tidak dapat dikompilasi.

Dari Boost FAQ: sumber

Mengetahui bahwa membutuhkan ==implementasi adalah beban , Anda tidak pernah ingin membuat beban tambahan dengan meminta !=implementasi juga.

Bagi saya pribadi ini tentang bagian S SOLID (desain berorientasi objek) - Prinsip substitusi Liskov: "objek dalam program harus dapat diganti dengan instance subtipe mereka tanpa mengubah kebenaran program itu.". Dalam hal ini adalah operator ! = Yang dapat saya ganti dengan == dan boolean inverse dalam logika boolean.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.