Double Negation ใน C ++


124

ฉันเพิ่งเข้ามาในโครงการที่มีฐานรหัสขนาดใหญ่มาก

ฉันจัดการกับ C ++ เป็นส่วนใหญ่และโค้ดจำนวนมากที่พวกเขาเขียนใช้การปฏิเสธสองครั้งสำหรับตรรกะบูลีน

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

ฉันรู้ว่าคนเหล่านี้เป็นโปรแกรมเมอร์อัจฉริยะเห็นได้ชัดว่าพวกเขาไม่ได้ทำสิ่งนี้โดยบังเอิญ

ฉันไม่ใช่ผู้เชี่ยวชาญ C ++ ที่ช่ำชองการเดาเพียงอย่างเดียวของฉันว่าทำไมพวกเขาถึงทำเช่นนี้ก็คือพวกเขาต้องการทำให้เป็นบวกอย่างแน่นอนว่าค่าที่ประเมินคือการแสดงบูลีนที่แท้จริง ดังนั้นพวกเขาจึงลบล้างมันแล้วลบมันอีกครั้งเพื่อให้มันกลับมาเป็นค่าบูลีนที่แท้จริง

ถูกต้องหรือฉันพลาดอะไรไป?



หัวข้อนี้ได้รับการกล่าวถึงที่นี่
Dima

คำตอบ:


122

มันเป็นเคล็ดลับในการแปลงเป็นบูล


19
คิดว่าแคสชัด ๆ ด้วย (บูล) จะชัดเจนกว่าทำไมใช้ยุ่งยากขนาดนี้ !! เพราะพิมพ์น้อย?
Baiyan Huang

27
อย่างไรก็ตามมันไม่มีจุดหมายใน C ++ หรือ C สมัยใหม่หรือเมื่อใช้ผลลัพธ์ในนิพจน์บูลีนเท่านั้น (เช่นในคำถาม) มันมีประโยชน์เมื่อเราไม่มีboolประเภทเพื่อช่วยหลีกเลี่ยงการจัดเก็บค่าอื่นที่ไม่ใช่1และ0ในตัวแปรบูลีน
Mike Seymour

6
@lzprgmr: การแคสต์อย่างชัดเจนทำให้เกิด"คำเตือนเกี่ยวกับประสิทธิภาพ"บน MSVC ใช้!!หรือ!=0แก้ปัญหาและในสองสิ่งนี้ฉันพบว่าตัวทำความสะอาดในอดีต (เนื่องจากมันจะทำงานกับประเภทต่างๆมากกว่า) นอกจากนี้ฉันยอมรับว่าไม่มีเหตุผลที่จะใช้อย่างใดอย่างหนึ่งในรหัสที่เป็นปัญหา
Yakov Galka

6
@ โนลโดรินฉันคิดว่ามันช่วยเพิ่มความสามารถในการอ่าน - ถ้าคุณรู้ว่ามันหมายถึงอะไรมันเรียบง่ายเรียบร้อยและมีเหตุผล
jwg

20
ปรับปรุง? Bloody hell ... ขอบางส่วนที่คุณสูบบุหรี่
Noldorin

73

มันเป็นสำนวนที่มีประโยชน์มากในบางบริบท ใช้มาโครเหล่านี้ (ตัวอย่างจากเคอร์เนล Linux) สำหรับ GCC มีการดำเนินการดังนี้:

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

ทำไมพวกเขาต้องทำเช่นนี้? GCC __builtin_expectถือว่าพารามิเตอร์เป็นlongไม่ใช่boolดังนั้นจึงต้องมีรูปแบบการแปลงบางรูปแบบ เนื่องจากพวกเขาไม่รู้ว่าcondเวลาเขียนมาโครเหล่านั้นคืออะไรจึงเป็นเรื่องปกติที่จะใช้ไฟล์!!สำนวน

พวกเขาอาจทำสิ่งเดียวกันได้โดยการเปรียบเทียบกับ 0 แต่ในความคิดของฉันมันตรงไปตรงมามากกว่าที่จะทำการลบล้างสองครั้งเนื่องจากเป็นสิ่งที่ใกล้เคียงที่สุดกับ cast-to-bool ที่ C มี

รหัสนี้สามารถใช้ใน C ++ ได้เช่นกัน ... มันเป็นตัวหารที่มีค่าน้อยที่สุด ถ้าเป็นไปได้ให้ทำสิ่งที่ใช้ได้ทั้ง C และ C ++


ฉันคิดว่านี่เป็นเรื่องที่สมเหตุสมผลมากเมื่อคุณคิดถึงมัน ฉันไม่ได้อ่านทุกคำตอบ แต่ดูเหมือนว่าจะไม่มีการระบุขั้นตอนการแปลง ถ้าเรามีค่า 2 บิตสูงและมีค่าสูงเพียงบิตเดียวเราจะมีค่าที่ไม่ใช่ศูนย์ การลบค่าที่ไม่ใช่ศูนย์จะทำให้เกิดการแปลงบูลีน (เป็นเท็จถ้าเป็นศูนย์หรือเป็นจริง) จากนั้นการลบอีกครั้งจะทำให้เกิดบูลีนที่แสดงถึงความจริงดั้งเดิม
Joey Carson

เนื่องจาก SO ไม่อนุญาตให้ฉันอัปเดตความคิดเห็นของฉันฉันจะเพิ่มการแก้ไขข้อผิดพลาดของฉัน การลบค่าอินทิกรัลจะทำให้เกิดการแปลงบูลีน (เป็นเท็จหากไม่ใช่ศูนย์หรือเป็นจริง)
Joey Carson

51

ผู้เขียนโค้ดคิดว่าจะแปลงตัวถูกดำเนินการเป็นบูล แต่เนื่องจากตัวถูกดำเนินการของ && ถูกแปลงเป็นบูลโดยปริยายแล้วจึงซ้ำซ้อนอย่างที่สุด


14
Visual C ++ ให้ประสิทธิภาพที่ลดลงในบางกรณีหากไม่มีเคล็ดลับนี้
Kirill V.Lyadvinsky

1
ฉันคิดว่ามันจะเป็นการดีที่สุดที่จะปิดการใช้งานคำเตือนแทนที่จะหลีกเลี่ยงคำเตือนที่ไร้ประโยชน์ในโค้ด
Ruslan

บางทีพวกเขาอาจไม่รู้ตัว สิ่งนี้มีความหมายที่สมบูรณ์แบบในบริบทของมาโครซึ่งคุณสามารถทำงานกับชนิดข้อมูลอินทิกรัลที่คุณไม่ทราบได้ พิจารณาอ็อบเจ็กต์ที่มีตัวดำเนินการวงเล็บมากเกินไปเพื่อส่งคืนค่าอินทิกรัลแทนฟิลด์บิต
Joey Carson

12

เป็นเทคนิคในการหลีกเลี่ยงการเขียน (ตัวแปร! = 0) - คือการแปลงจากประเภทใดก็ได้เป็นบูล

รหัส IMO เช่นนี้ไม่มีที่ใดในระบบที่จำเป็นต้องได้รับการดูแล - เนื่องจากรหัสนี้ไม่สามารถอ่านได้ในทันที (ด้วยเหตุนี้จึงเป็นคำถามในตอนแรก)

รหัสต้องชัดเจน - มิฉะนั้นคุณจะทิ้งมรดกแห่งหนี้เวลาไว้สำหรับอนาคตเนื่องจากต้องใช้เวลาในการทำความเข้าใจบางสิ่งที่ซับซ้อนโดยไม่จำเป็น


8
คำจำกัดความของฉันเกี่ยวกับเคล็ดลับคือสิ่งที่ทุกคนไม่สามารถเข้าใจได้ในการอ่านครั้งแรก สิ่งที่ต้องคิดออกเป็นเคล็ดลับ ยังสยองเพราะ! ผู้ปฏิบัติงานอาจทำงานหนักเกินไป ...
Richard Harrison

6
@ orlandu63: พิมพ์ดีดง่ายๆคือbool(expr)ทำในสิ่งที่ถูกต้องและทุกคนเข้าใจเจตนาตั้งแต่แรกเห็น !!(expr)เป็นการปฏิเสธสองครั้งซึ่งแปลงเป็นบูลโดยบังเอิญ ... นี่ไม่ใช่เรื่องง่าย
Adrien Plisson


9

ด้านข้าง - ขั้นตอนคำเตือนของคอมไพเลอร์ ลองสิ่งนี้:

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

บรรทัด "bar" สร้าง "ค่าบังคับให้บูล" จริง "หรือ" เท็จ "(คำเตือนเกี่ยวกับประสิทธิภาพ)" บน MSVC ++ แต่บรรทัด "baz" แอบดูได้ดี


1
พบบ่อยที่สุดใน Windows API ซึ่งไม่ทราบเกี่ยวกับboolประเภท - ทุกอย่างเข้ารหัสเป็น0หรือ1ในintไฟล์.
Mark Ransom

4

เป็นผู้ดำเนินการ! มากเกินไป?
ถ้าไม่เป็นเช่นนั้นพวกเขาอาจทำเช่นนี้เพื่อแปลงตัวแปรเป็นบูลโดยไม่มีการเตือน นี่ไม่ใช่วิธีมาตรฐานในการทำสิ่งต่างๆอย่างแน่นอน


4

นักพัฒนามรดก C ไม่มีชนิดบูลีนเพื่อให้พวกเขามักจะ#define TRUE 1และ#define FALSE 0และนำไปใช้โดยพลการประเภทข้อมูลที่เป็นตัวเลขสำหรับการเปรียบเทียบบูลีน ตอนนี้เรามีboolแล้วคอมไพเลอร์จำนวนมากจะส่งเสียงเตือนเมื่อมีการกำหนดและการเปรียบเทียบบางประเภทโดยใช้ส่วนผสมของประเภทตัวเลขและประเภทบูลีน การใช้งานทั้งสองนี้จะขัดแย้งกันในที่สุดเมื่อทำงานกับรหัสเดิม

เพื่อหลีกเลี่ยงปัญหานี้นักพัฒนาบางรายใช้ข้อมูลประจำตัวบูลีนต่อไปนี้: !num_valueส่งกลับbool trueif num_value == 0; falseมิฉะนั้น. !!num_valueผลตอบแทนbool falseถ้าnum_value == 0; trueมิฉะนั้น. การปฏิเสธเพียงครั้งเดียวก็เพียงพอที่จะแปลงnum_valueเป็นbool; อย่างไรก็ตามการปฏิเสธสองครั้งเป็นสิ่งจำเป็นเพื่อคืนความรู้สึกดั้งเดิมของนิพจน์บูลีน

รูปแบบนี้เรียกว่าสำนวนกล่าวคือสิ่งที่คนคุ้นเคยกับภาษาใช้กันทั่วไป ดังนั้นฉันจึงไม่เห็นว่ามันเป็นการต่อต้านรูปแบบมากเท่าที่ฉันจะทำได้static_cast<bool>(num_value)ทำได้ การแคสต์อาจให้ผลลัพธ์ที่ถูกต้องเป็นอย่างดี แต่คอมไพเลอร์บางตัวก็ส่งเสียงเตือนเกี่ยวกับประสิทธิภาพดังนั้นคุณยังคงต้องจัดการกับสิ่งนั้น

วิธีอื่น ๆ (num_value != FALSE)ที่อยู่นี้คือการพูด ฉันก็โอเคเช่นกัน แต่โดยรวมแล้ว!!num_valueเป็น verbose น้อยกว่ามากอาจจะชัดเจนกว่าและไม่สับสนในครั้งที่สองที่คุณเห็น


2

!! ถูกใช้เพื่อรับมือกับ C ++ ดั้งเดิมซึ่งไม่มีประเภทบูลีน (เช่นเดียวกับ C)


ตัวอย่างปัญหา:

ภายในif(condition)ที่conditionตอบสนองความต้องการในการประเมินเพื่อบางชนิดเช่นdouble, int, void*ฯลฯ แต่ไม่ได้boolมันไม่ได้อยู่เลย

สมมติว่ามีคลาสอยู่int256(จำนวนเต็ม 256 บิต) และการแปลงจำนวนเต็ม / แคสต์ทั้งหมดมีมากเกินไป

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

หากต้องการทดสอบว่าxเป็น "จริง" หรือไม่เป็นศูนย์ให้if (x)แปลงxเป็นจำนวนเต็มแล้วประเมินว่าintไม่ใช่ศูนย์ การโอเวอร์โหลดโดยทั่วไป(int) xจะส่งคืนเฉพาะ LSbits ของx. if (x)เป็นเพียงการทดสอบ LSbits ของx.

แต่ C ++ มีตัว!ดำเนินการ !xโดยทั่วไปแล้วการโอเวอร์โหลดจะประเมินบิตทั้งหมดของx. ดังนั้นเพื่อให้ได้กลับไปตรรกะไม่ใช่คว่ำif (!!x)ถูกนำมาใช้

Ref รุ่นเก่าของ C ++ ใช้โอเปอเรเตอร์ "int" ของคลาสเมื่อประเมินเงื่อนไขในคำสั่ง "if ()" หรือไม่


1

ในฐานะที่เป็นMarcinกล่าวได้เป็นอย่างดีอาจจะสำคัญว่าถ้าไปประกอบอยู่ในการเล่น มิฉะนั้นใน C / C ++ จะไม่สำคัญยกเว้นว่าคุณกำลังทำสิ่งใดสิ่งหนึ่งต่อไปนี้:

  • การเปรียบเทียบโดยตรงกับtrue(หรือใน C เช่นTRUEมาโคร) ซึ่งเป็นความคิดที่ไม่ดี ตัวอย่างเช่น:

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

  • คุณเพียงแค่ต้องการให้บางสิ่งบางอย่างถูกแปลงเป็นค่า 0/1 ที่เข้มงวด ใน C ++ การมอบหมายให้ a boolจะทำสิ่งนี้โดยปริยาย (สำหรับสิ่งที่แปลงสภาพได้โดยปริยายbool) ใน C หรือถ้าคุณกำลังจัดการกับตัวแปรที่ไม่ใช่บูลนี่เป็นสำนวนที่ฉันเคยเห็น แต่ฉันชอบ(some_variable != 0)ความหลากหลายด้วยตัวเอง

ฉันคิดว่าในบริบทของนิพจน์บูลีนที่ใหญ่กว่านั้นมันทำให้สิ่งต่าง ๆ กระจัดกระจาย


1

หากตัวแปรเป็นประเภทวัตถุก็อาจมี! กำหนดตัวดำเนินการ แต่ไม่มีการร่ายเพื่อบูล (หรือแย่กว่านั้นคือการร่ายโดยปริยายเป็น int ด้วยความหมายที่แตกต่างกันการเรียกใช้ตัวดำเนินการ! สองครั้งจะทำให้เกิดการแปลงเป็นบูลที่ใช้งานได้แม้ในกรณีแปลก ๆ


0

ถูกต้อง แต่ใน C ไม่มีจุดหมายที่นี่ - 'if' และ '&&' จะปฏิบัติต่อนิพจน์ในลักษณะเดียวกันโดยไม่มี "!!"

ฉันคิดว่าเหตุผลที่ต้องทำใน C ++ ก็คือ '&&' อาจจะทำงานมากเกินไป แต่แล้วจึงจะทำได้ '' จึงไม่ได้จริงๆรับประกันว่าคุณจะได้รับบูลโดยไม่ต้องมองหาที่รหัสสำหรับประเภทของและvariable api.callอาจมีคนที่มีประสบการณ์ C ++ มากกว่าสามารถอธิบายได้ บางทีอาจหมายถึงมาตรการเชิงลึกเชิงป้องกันไม่ใช่การรับประกัน


คอมไพเลอร์จะปฏิบัติต่อค่าในลักษณะเดียวกันหากใช้เป็นตัวถูกดำเนินการกับifหรือ&&แต่การใช้!!อาจช่วยในคอมไพเลอร์บางตัวหากif (!!(number & mask))ถูกแทนที่ด้วยbit triggered = !!(number & mask); if (triggered); ในคอมไพเลอร์แบบฝังบางตัวที่มีประเภทบิตการกำหนดเช่น 256 เป็นประเภทบิตจะให้ค่าเป็นศูนย์ หากไม่มีการ!!แปลงสภาพที่ปลอดภัยอย่างเห็นได้ชัด (การคัดลอกifเงื่อนไขไปยังตัวแปรแล้วแตกแขนง) จะไม่ปลอดภัย
supercat

0

บางทีโปรแกรมเมอร์กำลังคิดอะไรแบบนี้ ...

!! myAnswer เป็นบูลีน ในบริบทมันควรจะกลายเป็นบูลีน แต่ฉันชอบที่จะปังปัง ๆ เพื่อให้แน่ใจเพราะกาลครั้งหนึ่งมีแมลงลึกลับที่กัดฉันและปังปังฉันก็ฆ่ามัน


0

นี่อาจเป็นตัวอย่างของเคล็ดลับดับเบิ้ลปังดูThe Safe Bool Idiomสำหรับรายละเอียดเพิ่มเติม ที่นี่ฉันสรุปหน้าแรกของบทความ

ใน C ++ มีหลายวิธีในการจัดเตรียมการทดสอบบูลีนสำหรับคลาส

วิธีที่ชัดเจนคือoperator boolตัวดำเนินการแปลง

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

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

เราสามารถทดสอบชั้นเรียน

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

แต่opereator boolถือว่าไม่ปลอดภัยเพราะจะช่วยให้การดำเนินงานไร้สาระเช่นหรือtest << 1;int i=test

การใช้operator!จะปลอดภัยกว่าเนื่องจากเราหลีกเลี่ยงปัญหาการแปลงโดยปริยายหรือการใช้งานมากเกินไป

การใช้งานเป็นเรื่องเล็กน้อย

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

สองวิธีทางสำนวนในการทดสอบTestableวัตถุคือ

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

รุ่นแรกif (!!test)เป็นสิ่งที่บางคนเรียกว่าเคล็ดลับคู่ปัง


1
ตั้งแต่ C ++ 11 สามารถใช้ได้ explicit operator boolเพื่อป้องกันการแปลงโดยนัยเป็นอินทิกรัลประเภทอื่น ๆ
Arne Vogel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.