การใช้ if (0) เพื่อข้ามเคสในสวิตช์ควรจะใช้งานได้หรือไม่?


122

ฉันมีสถานการณ์ที่ฉันต้องการสองกรณีในคำสั่งสลับ C ++ ให้ทั้งสองตกไปอยู่ในกรณีที่สาม โดยเฉพาะกรณีที่สองจะตกไปสู่กรณีที่สามและกรณีแรกก็จะตกไปสู่กรณีที่สามโดยไม่ผ่านกรณีที่สอง

ฉันมีความคิดโง่ ๆ ลองทำดูและได้ผล! ฉันห่อกรณีที่สองใน...if (0) { }ดูเหมือนว่า:

#ifdef __cplusplus
#  include <cstdio>
#else
#  include <stdio.h>
#endif

int main(void) {
    for (int i = 0; i < 3; i++) {
        printf("%d: ", i);
        switch (i) {
        case 0:
            putchar('a');
            // @fallthrough@
            if (0) {        // fall past all of case 1 (!)
        case 1:
            putchar('b');
            // @fallthrough@
            }
        case 2:
            putchar('c');
            break;
        }
        putchar('\n');
    }
    return 0;
}

เมื่อฉันเรียกใช้ฉันจะได้ผลลัพธ์ที่ต้องการ:

0: ac
1: bc
2: c

ฉันลองใช้ทั้ง C และ C ++ (ทั้งที่มีเสียงดัง) และมันก็ทำแบบเดียวกัน

คำถามของฉันคือ C / C ++ นี้ถูกต้องหรือไม่ มันควรจะทำอย่างไร?


34
ใช่สิ่งนี้ถูกต้องและใช้ได้กับเหตุผลเดียวกันกับที่อุปกรณ์ของ Duffทำ
dxiv

42
โปรดทราบว่าโค้ดเช่นนี้จะช่วยให้คุณไม่ต้องใช้คำแนะนำแบบโค้ดใด ๆ ในสภาพแวดล้อมที่ใส่ใจแม้เพียงเล็กน้อยเกี่ยวกับความสามารถในการอ่านและการบำรุงรักษา
Andrew Henle

16
นี่มันน่ากลัว น่ากลัวยิ่งกว่าอุปกรณ์ของ Duff คุณต้องนึกถึง switch(x) { case A: case B: do_this(); if(x == B) also_do_that(); ... }ที่เกี่ยวข้องผมยังเพิ่งเห็นสิ่งที่ชอบ นั่นก็คือ IMO ที่น่ากลัว กรุณาเขียนสิ่งที่ต้องการออกมาราวกับว่าข้อความแม้ว่าจะหมายความว่าคุณต้องทำซ้ำหนึ่งบรรทัดในสองตำแหน่ง ใช้ฟังก์ชันและตัวแปร (และเอกสารประกอบ!) เพื่อลดความเสี่ยงในการอัปเดตโดยไม่ตั้งใจในภายหลังเพียงที่เดียว
ilkkachu

50
:-) สำหรับผู้ที่ได้รับบาดเจ็บหรือพิการเนื่องจากดูรหัสนั้นฉันไม่ได้บอกว่ามันเป็นความคิดที่ดี อันที่จริงฉันว่ามันเป็นความคิดที่โง่เขลา
Mark Adler

4
โปรดทราบว่าโครงสร้างภายในสวิตช์เช่นนี้ไม่สามารถเล่นได้ดีกับ RAII :(
Mooing Duck

คำตอบ:


58

ใช่สิ่งนี้ได้รับอนุญาตและทำในสิ่งที่คุณต้องการ สำหรับswitchคำสั่งมาตรฐาน C ++ กล่าวว่า :

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

[หมายเหตุ 1: โดยปกติสารตั้งต้นที่เป็นหัวเรื่องของสวิตช์คือสารประกอบและตัวพิมพ์เล็กและเลเบลเริ่มต้นจะปรากฏในคำสั่งระดับบนสุดที่อยู่ภายในส่วนย่อย (สารประกอบ) แต่ไม่จำเป็นต้องใช้ การประกาศสามารถปรากฏในส่วนย่อยของคำสั่งสวิตช์ - หมายเหตุ]

ดังนั้นเมื่อifคำสั่งได้รับการประเมินขั้นตอนการควบคุมจะดำเนินการตามกฎของifคำสั่งโดยไม่คำนึงถึงป้ายกำกับกรณีแทรกแซง


13
ในกรณีนี้เราสามารถดูBoost ได้โครูทีนสำหรับชุดมาโครปั่นกระเพาะอาหารซึ่งใช้ประโยชน์จากกฎนี้ (และกรณีมุมอื่น ๆ อีกครึ่งโหลของ C ++) เพื่อใช้โครูทีนโดยใช้กฎ C ++ 03 พวกเขาไม่ได้เป็นส่วนหนึ่งของภาษาจนถึง C ++ 20 แต่มาโครเหล่านี้ทำให้มันใช้งานได้ ... ตราบใดที่คุณกินยาลดกรดก่อน!
Cort Ammon

60

ใช่นี่ควรจะใช้งานได้ เลเบลเคสสำหรับคำสั่งสวิตช์ใน C เกือบจะเหมือนกับป้ายกำกับ goto (โดยมีข้อแม้บางประการเกี่ยวกับวิธีการทำงานกับคำสั่งสวิตช์ที่ซ้อนกัน) โดยเฉพาะอย่างยิ่งพวกเขาไม่ได้กำหนดบล็อกสำหรับข้อความที่คุณคิดว่าเป็น "ในกรณี" และคุณสามารถใช้มันเพื่อกระโดดลงไปตรงกลางบล็อกได้เช่นเดียวกับที่คุณสามารถทำได้โดยใช้ goto เมื่อกระโดดลงไปตรงกลางบล็อกคำเตือนเช่นเดียวกับ goto ใช้เกี่ยวกับการกระโดดข้ามการเริ่มต้นของตัวแปรเป็นต้น

จากที่กล่าวมาในทางปฏิบัติอาจชัดเจนกว่าที่จะเขียนสิ่งนี้ด้วยคำสั่ง goto ดังใน:

    switch (i) {
    case 0:
        putchar('a');
        goto case2;
    case 1:
        putchar('b');
        // @fallthrough@
    case2:
    case 2:
        putchar('c');
        break;
    }

1
"เมื่อกระโดดลงไปตรงกลางบล็อกคำเตือนเช่นเดียวกับ goto ใช้เกี่ยวกับการกระโดดข้ามการเริ่มต้นของตัวแปร ฯลฯ "สิ่งเหล่านี้จะเป็นข้อแม้อะไร? คุณไม่สามารถข้ามการเริ่มต้นของตัวแปรได้
ดาวเคราะห์น้อยมีปีก

4
@AsteroidsWithWings คุณหมายถึง "ไม่สามารถ" จากมุมมองที่เป็นไปตามมาตรฐานหรือจากมุมมองในทางปฏิบัติที่คอมไพเลอร์จะไม่อนุญาต? เนื่องจาก GCC ของฉันอนุญาตให้อยู่ในโหมด C พร้อมคำเตือน ไม่อนุญาตให้ใช้ในโหมด C ++
ilkkachu

5
@quetzalcoatl gcc-9 และ clang-6 ทั้งคู่อนุญาตให้ใช้รหัสนี้คำเตือนเกี่ยวกับการไม่ได้เริ่มต้นbee(ในโหมด C ใน C ++ จะเกิดข้อผิดพลาด "ไม่สามารถข้ามจากคำสั่งสวิตช์ไปยังป้ายกำกับกรณีนี้ได้ / ข้ามการเริ่มต้นตัวแปร")
Ruslan

7
คุณรู้ว่าคุณทำอะไรผิดพลาดเมื่อใช้gotoเป็นวิธีที่สะอาดกว่า
Konrad Rudolph

9
@KonradRudolph: gotoถูกทำร้ายมาก แต่ก็ไม่มีอะไรน่ารังเกียจหากคุณไม่ได้สร้างโครงสร้างการควบคุมที่ซับซ้อนออกมาด้วยตนเอง "goto ถือว่าเป็นอันตราย" ทั้งหมดเกี่ยวกับการเขียนโค้ด HLL (เช่น C) ที่ดูเหมือน asm ที่คอมไพเลอร์ปล่อยออกมา (jmps ไปมาทั่วทุกที่) มีการใช้ goto ที่มีโครงสร้างที่ดีมากมายเช่นการส่งต่ออย่างเดียว (แนวคิดไม่แตกต่างจากbreakหรือเร็วที่สุดreturn) ไม่น่าลองซ้ำ - วนซ้ำเท่านั้น (หลีกเลี่ยงการปิดบังเส้นทางการไหลทั่วไปและชดเชยการขาดการซ้อน - continue) ฯลฯ
R .. GitHub STOP HELPING ICE

28

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

นี่คือเหตุผลที่switch ... caseโดยปกติแล้วคำสั่งควรเขียนด้วยการเรียกใช้ฟังก์ชันไม่ใช่โค้ดอินไลน์จำนวนมาก

switch(i) {
case 0:
    do_zero_case(); do_general_stuff(); break;
case 1:
    do_one_case(); do_general_stuff(); break;
case 2:
    do_general_stuff(); break;
default:
    do_default_not_zero_not_one_not_general_stuff(); break;
}

โปรดทราบว่ารหัสในคำถามไม่ได้do_general_stuffสำหรับค่าเริ่มต้นเพียงสำหรับกรณีที่ 2 (และ 0 และ 1) แม้ว่าจะไม่เรียกใช้สวิตช์iนอกช่วง 0..2
Peter Cordes

@PeterCordes - โปรดทราบว่ารหัสในคำตอบไม่ได้do_general_stuffเป็นค่าเริ่มต้นสำหรับกรณีที่ 2 (และ 0 และ 1) เท่านั้น
พีทเบ็คเกอร์

ข้อเสนอแนะ - บางทีกรณีเริ่มต้นควรถูกลบออกหรือเปลี่ยนชื่อ? เป็นเรื่องง่ายมากที่จะพลาดชื่อฟังก์ชันต่างๆ
ฉัน

@PeteBecker: โอ้ IDK ฉันพลาดไปได้อย่างไรgeneralและdefaultเป็นคำที่แตกต่างกัน OTOH เป็นความจริงที่ทราบกันดีว่าโดยปกติแล้วมนุษย์ยังสามารถอ่านคำศัพท์ได้หากตัวอักษรกลางมีสัญญาณรบกวน เรามักจะมองไปที่จุดเริ่มต้นและจุดสิ้นสุดดังนั้นการมีเพียงตรงกลางเท่านั้นที่แตกต่างกันจึงอาจไม่เหมาะสำหรับความสามารถในการข้ามได้ บางทีอาจลบdo_คำนำหน้า
Peter Cordes

1
@supercat: "ข้อเท็จจริงที่เป็นที่รู้จัก" นั้นไม่ได้เกี่ยวกับการแทนที่ตัวอักษรกลางด้วยตัวอักษรที่แตกต่างกัน แต่เป็นเพียงการผสมลำดับจากนั้น "ถ้า cialm เป็นจริงใน graneel คุณก็จะต้องถูก raed tihs"
Michael Karcher
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.