Negasi Ganda dalam C ++


124

Saya baru saja masuk ke proyek dengan basis kode yang cukup besar.

Saya kebanyakan berurusan dengan C ++ dan banyak kode yang mereka tulis menggunakan negasi ganda untuk logika boolean mereka.

 if (!!variable && (!!api.lookup("some-string"))) {
       do_some_stuff();
 }                                   

Saya tahu orang-orang ini adalah pemrogram yang cerdas, jelas mereka tidak melakukan ini secara tidak sengaja.

Saya bukan ahli C ++ berpengalaman, satu-satunya dugaan saya mengapa mereka melakukan ini adalah bahwa mereka ingin memastikan bahwa nilai yang dievaluasi adalah representasi boolean yang sebenarnya. Jadi mereka meniadakannya, lalu meniadakannya lagi untuk mengembalikannya ke nilai boolean aslinya.

Apakah ini benar, atau saya melewatkan sesuatu?



Topik ini telah dibahas di sini .
Dima

Jawaban:


121

Ini adalah trik untuk mengubahnya menjadi bool.


19
Saya pikir cast secara eksplisit dengan (bool) akan lebih jelas, mengapa menggunakan trik ini !!, karena kurang mengetik?
Baiyan Huang

27
Namun, ini tidak ada gunanya di C ++ atau C modern, atau jika hasilnya hanya digunakan dalam ekspresi boolean (seperti dalam pertanyaan). Itu berguna kembali ketika kita tidak memiliki booltipe, untuk membantu menghindari menyimpan nilai selain 1dan 0dalam variabel boolean.
Mike Seymour

6
@ lzprgmr: pemeran eksplisit menyebabkan "peringatan kinerja" di MSVC. Menggunakan !!atau !=0memecahkan masalah, dan di antara keduanya saya menemukan pembersih sebelumnya (karena ini akan bekerja pada lebih banyak jenis). Saya juga setuju bahwa tidak ada alasan untuk menggunakan salah satu dari kode tersebut.
Yakov Galka

6
@Noldorin, menurut saya ini meningkatkan keterbacaan - jika Anda tahu artinya, itu sederhana, rapi dan logis.
jwg

19
Meningkatkan? Sialan ... berikan apa pun yang kamu merokok.
Noldorin

73

Ini sebenarnya adalah idiom yang sangat berguna dalam beberapa konteks. Ambil makro ini (contoh dari kernel Linux). Untuk GCC, penerapannya sebagai berikut:

#define likely(cond)   (__builtin_expect(!!(cond), 1))
#define unlikely(cond) (__builtin_expect(!!(cond), 0))

Mengapa mereka harus melakukan ini? GCC __builtin_expectmemperlakukan parameternya sebagai longdan tidak bool, jadi perlu ada beberapa bentuk konversi. Karena mereka tidak tahu apa conditu saat mereka menulis makro tersebut, yang paling umum adalah menggunakan !!idiom.

Mereka mungkin dapat melakukan hal yang sama dengan membandingkannya dengan 0, tetapi menurut saya, sebenarnya lebih mudah untuk melakukan negasi ganda, karena itulah yang paling dekat dengan cast-to-bool yang dimiliki C.

Kode ini dapat digunakan di C ++ juga ... ini adalah penyebut yang paling rendah. Jika memungkinkan, lakukan apa yang berhasil di C dan C ++.


Saya pikir ini sangat masuk akal ketika Anda memikirkannya. Saya belum membaca setiap jawaban, tetapi sepertinya proses konversinya tidak ditentukan. Jika kita memiliki nilai dengan tinggi 2 bit dan nilai yang hanya memiliki satu bit tinggi, kita akan memiliki nilai bukan nol. Meniadakan nilai bukan nol menghasilkan konversi boolean (salah jika nol, benar jika tidak). Kemudian meniadakan kembali menghasilkan boolean yang mewakili kebenaran asli.
Joey Carson

Karena SO tidak mengizinkan saya memperbarui komentar saya, saya akan menambahkan perbaikan pada kesalahan saya. Meniadakan nilai integral menghasilkan konversi boolean (salah jika bukan nol, benar jika tidak).
Joey Carson

51

Para pembuat kode berpikir bahwa itu akan mengubah operan menjadi bool, tetapi karena operan && sudah secara implisit diubah menjadi bool, itu benar-benar berlebihan.


14
Visual C ++ memberikan kinerja yang berkurang dalam beberapa kasus tanpa trik ini.
Kirill V. Lyadvinsky

1
Saya kira akan lebih baik untuk menonaktifkan peringatan daripada mengatasi peringatan yang tidak berguna dalam kode.
Ruslan

Mungkin mereka tidak menyadarinya. Ini sangat masuk akal dalam konteks makro, di mana Anda bisa bekerja dengan tipe data integral yang tidak Anda sadari. Pertimbangkan objek dengan operator tanda kurung kelebihan beban untuk mengembalikan nilai integral yang mewakili bidang bit.
Joey Carson

12

Ini adalah teknik untuk menghindari penulisan (variabel! = 0) - yaitu untuk mengubah dari jenis apa pun itu menjadi bool.

Kode IMO seperti ini tidak memiliki tempat dalam sistem yang perlu dijaga - karena ini bukan kode yang langsung dapat dibaca (oleh karena itu pertanyaannya di tempat pertama).

Kode harus dapat dibaca - jika tidak, Anda meninggalkan warisan hutang waktu untuk masa depan - karena diperlukan waktu untuk memahami sesuatu yang berbelit-belit.


8
Definisi saya tentang trik adalah sesuatu yang tidak semua orang bisa mengerti pada bacaan pertama. Sesuatu yang perlu dipikirkan adalah tipuan. Juga mengerikan karena! Operator bisa kelebihan beban ...
Richard Harrison

6
@ orlandu63: typecasting sederhana adalah bool(expr): ia melakukan hal yang benar dan semua orang memahami maksudnya pada pandangan pertama. !!(expr)adalah negasi ganda, yang secara tidak sengaja diubah menjadi bool ... ini tidak sederhana.
Adrien Plisson

12

Ya itu benar dan tidak, Anda tidak melewatkan sesuatu. !!adalah konversi ke bool. Lihat pertanyaan ini untuk pembahasan lebih lanjut.


9

Ini mengesampingkan peringatan compiler. Coba ini:

int _tmain(int argc, _TCHAR* argv[])
{
    int foo = 5;
    bool bar = foo;
    bool baz = !!foo;
    return 0;
}

Baris 'bar' menghasilkan "nilai paksa ke bool 'true' atau 'false' (peringatan kinerja)" di MSVC ++, tetapi baris 'baz' menyelinap melalui denda.


1
Paling sering ditemui di Windows API itu sendiri yang tidak tahu tentang booltipenya - semuanya dikodekan sebagai 0atau 1dalam file int.
Mark Ransom

4

Apakah operator! kelebihan beban?
Jika tidak, mereka mungkin melakukan ini untuk mengubah variabel menjadi bool tanpa menghasilkan peringatan. Ini jelas bukan cara standar dalam melakukan sesuatu.


4

Legacy C pengembang tidak punya tipe Boolean, sehingga mereka sering #define TRUE 1dan #define FALSE 0kemudian digunakan sewenang-wenang jenis data numerik untuk perbandingan Boolean. Sekarang yang kita miliki bool, banyak kompiler akan mengeluarkan peringatan ketika beberapa tipe tugas dan perbandingan dibuat menggunakan campuran tipe numerik dan tipe Boolean. Kedua penggunaan ini pada akhirnya akan bertabrakan saat bekerja dengan kode lama.

Untuk mengatasi masalah ini, beberapa pengembang menggunakan identitas Boolean berikut: !num_valuereturn bool trueif num_value == 0; falsejika tidak. !!num_valuekembali bool falsejika num_value == 0; truejika tidak. Negasi tunggal sudah cukup untuk diubah num_valuemenjadi bool; namun, negasi ganda diperlukan untuk mengembalikan pengertian asli dari ekspresi Boolean.

Pola ini dikenal sebagai idiom , yaitu sesuatu yang biasa digunakan oleh orang yang mengenal bahasa tersebut. Oleh karena itu, saya tidak melihatnya sebagai anti-pola, sebanyak yang saya inginkan static_cast<bool>(num_value). Pemeran mungkin memberikan hasil yang benar dengan baik, tetapi beberapa kompiler kemudian mengeluarkan peringatan kinerja, jadi Anda masih harus mengatasinya.

Cara lain untuk mengatasi hal ini adalah dengan mengatakan (num_value != FALSE),. Saya baik-baik saja dengan itu juga, tetapi secara keseluruhan, !!num_valuejauh lebih tidak bertele-tele, mungkin lebih jelas, dan tidak membingungkan untuk kedua kalinya Anda melihatnya.


2

!! digunakan untuk mengatasi C ++ asli yang tidak memiliki tipe boolean (seperti halnya C).


Contoh Soal:

Di dalam if(condition), conditionkebutuhan untuk mengevaluasi beberapa jenis seperti double, int, void*, dll, tetapi tidak boolkarena belum ada.

Misalnya ada kelas int256(bilangan bulat 256 bit) dan semua konversi / pemeran bilangan bulat kelebihan beban.

int256 x = foo();
if (x) ...

Untuk menguji apakah xitu "benar" atau bukan nol, if (x)akan mengonversi xke beberapa bilangan bulat dan kemudian menilai apakah intitu bukan nol. Kelebihan beban khas (int) xhanya akan mengembalikan LSbit dari x. if (x)kemudian hanya menguji LSbits dari x.

Tetapi C ++ memiliki !operator. Sebuah kelebihan beban !xbiasanya akan mengevaluasi semua bit x. Jadi untuk kembali ke logika non-terbalik if (!!x)digunakan.

Ref Apakah versi C ++ yang lebih lama menggunakan operator `int` dari sebuah kelas saat mengevaluasi kondisi dalam pernyataan` if () `?


1

Seperti yang disebutkan Marcin , mungkin penting jika operator kelebihan beban sedang bermain. Jika tidak, di C / C ++ tidak masalah kecuali jika Anda melakukan salah satu hal berikut:

  • perbandingan langsung dengan true(atau dalam C sesuatu seperti TRUEmakro), yang hampir selalu merupakan ide yang buruk. Sebagai contoh:

    if (api.lookup("some-string") == true) {...}

  • Anda hanya ingin sesuatu diubah menjadi nilai 0/1 yang ketat. Dalam C ++ sebuah penugasan ke a boolakan melakukan ini secara implisit (untuk hal-hal yang secara implisit dapat dikonversi bool). Di C atau jika Anda berurusan dengan variabel non-bool, ini adalah idiom yang pernah saya lihat, tapi saya sendiri lebih suka (some_variable != 0)variasinya.

Saya pikir dalam konteks ekspresi boolean yang lebih besar itu hanya mengacaukan segalanya.


1

Jika variabel berjenis objek, variabel itu mungkin memiliki a! ditentukan oleh operator tetapi tidak ada transmisi ke bool (atau lebih buruk lagi, transmisi implisit ke int dengan semantik berbeda. Memanggil! operator dua kali menghasilkan konversi ke bool yang berfungsi bahkan dalam kasus yang aneh.


0

Itu benar tetapi, di C, tidak ada gunanya di sini - 'jika' dan '&&' akan memperlakukan ekspresi tersebut dengan cara yang sama tanpa '!!'.

Alasan untuk melakukan ini di C ++, saya kira, adalah bahwa '&&' bisa jadi kelebihan beban. Tapi kemudian, begitu juga '!', Jadi tidak benar - benar menjamin Anda mendapatkan bool, tanpa melihat kode untuk jenis variabledan api.call. Mungkin seseorang dengan lebih banyak pengalaman C ++ bisa menjelaskan; mungkin itu dimaksudkan sebagai semacam ukuran pertahanan mendalam, bukan jaminan.


Kompilator akan memperlakukan nilai dengan cara yang sama jika hanya digunakan sebagai operan untuk ifatau &&, tetapi penggunaan !!mungkin membantu pada beberapa kompiler jika if (!!(number & mask))diganti dengan bit triggered = !!(number & mask); if (triggered); pada beberapa kompiler tertanam dengan tipe bit, menugaskan misalnya 256 ke tipe bit akan menghasilkan nol. Tanpa !!, transformasi yang tampaknya aman (menyalin ifkondisi ke variabel dan kemudian bercabang) tidak akan aman.
supercat

0

Mungkin programmernya memikirkan hal seperti ini ...

!! myAnswer adalah boolean. Dalam konteksnya, itu harus menjadi boolean, tapi saya hanya suka bang bang untuk memastikan, karena suatu ketika ada bug misterius yang menggigit saya, dan bang bang, saya membunuhnya.


0

Ini mungkin contoh dari trik double-bang , lihat The Safe Bool Idiom untuk lebih jelasnya. Di sini saya merangkum halaman pertama artikel.

Di C ++ ada beberapa cara untuk menyediakan pengujian Boolean untuk kelas.

Cara yang jelas adalah operator booloperator konversi.

// operator bool version
  class Testable {
    bool ok_;
  public:
    explicit Testable(bool b=true):ok_(b) {}

    operator bool() const { // use bool conversion operator
      return ok_;
    }
  };

Kita bisa menguji kelasnya,

Testable test;
  if (test) 
    std::cout << "Yes, test is working!\n";
  else 
    std::cout << "No, test is not working!\n";

Namun, opereator booldianggap tidak aman karena memungkinkan operasi yang tidak masuk akal seperti test << 1;atau int i=test.

Menggunakan operator!lebih aman karena kami menghindari konversi implisit atau masalah kelebihan muatan.

Implementasinya sepele,

bool operator!() const { // use operator!
    return !ok_;
  }

Dua cara idiomatik untuk menguji Testableobjek adalah

  Testable test;
  if (!!test) 
    std::cout << "Yes, test is working!\n";
  if (!test2) {
    std::cout << "No, test2 is not working!\n";

Versi pertama if (!!test)adalah apa yang oleh sebagian orang disebut sebagai trik double-bang .


1
Sejak C ++ 11, seseorang dapat menggunakan explicit operator booluntuk mencegah konversi implisit ke tipe integral lainnya.
Arne Vogel
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.