C++ C++;隐式直通开关上的强制编译时错误/警告

C++ C++;隐式直通开关上的强制编译时错误/警告,c++,switch-statement,compiler-warnings,C++,Switch Statement,Compiler Warnings,switch语句可能非常有用,但会导致程序员忘记break语句的常见错误: switch(val) { case 0: foo(); break; case 1: bar(); // oops case 2: baz(); break; default: roomba(); } 你不会得到一个明显的警告,因为有时明显需要失败。好的编码风格建议在你的失败

switch
语句可能非常有用,但会导致程序员忘记break语句的常见错误:

switch(val) {
    case 0:
        foo();
        break;
    case 1:
        bar();
        // oops
    case 2:
        baz();
        break;
    default:
        roomba();
}
你不会得到一个明显的警告,因为有时明显需要失败。好的编码风格建议在你的失败是故意的时候发表评论,但有时这是不够的


我很确定这个问题的答案是否定的,但是:如果您的
案例
没有至少一个
中断,那么当前(或将来提出的)有没有办法让编译器抛出一个错误(或者至少是一个警告!)或类似于
//fallthru的东西?最好有一个防御性编程选项来使用
switch
语句

Well clang有
-Wimplicit漏洞
,我不知道,但通过使用
-Weverything
找到了它。因此,对于这段代码,它给了我以下警告():

我能找到的有关此标志的唯一文档位于中,其中说明:

clang::fallthrough属性与 -Wimplicit fallthrough参数,用于注释交换机标签之间的故意故障。它只能应用于null语句 位于任何语句和下一语句之间的执行点 开关标签。通常用特定的标记标记这些位置 注释,但此属性用于将注释替换为更多注释 严格注释,可由编译器检查

并提供了一个如何标记显式失败的示例:

case 44:  // warning: unannotated fall-through
g();
[[clang::fallthrough]];
case 55:  // no warning
这种使用标记显式跳转的缺点是不可移植<代码>Visual Studio生成错误,并且
gcc
生成以下警告:

warning: attributes at the beginning of statement are ignored [-Wattributes]
如果要使用
-Werror
,这是一个问题

我用
gcc4.9
尝试了这一点,但它似乎不支持此警告:

错误:无法识别的命令行选项“-Wimplicit fallthrough”

截至,支持
-Wimplicit故障排除
,并且当故意故障排除时,可使用
\uuuuuu属性((故障排除))
抑制警告。GCC确实在某些场景中识别“故障”注释,但可能会混淆

我看不到为
VisualStudio
生成此类警告的方法

注意,说明了
-Weverything
不用于生产:

这是一个疯狂的团体,它可以发出叮当声的警告。 不要在代码中使用此选项。它完全是为了叮当作响 开发人员或应用程序,以探索存在哪些警告

但它对于找出存在哪些警告是有用的

C++17更改 在C++17中,我们得到的属性[[fallthrough]]包含在:

属性令牌故障可应用于空语句(9.2);这样的声明是一个错误 陈述属性标记故障最多应在每个属性列表中出现一次,且无attributeargument- 第条应在场。fallthrough语句只能出现在封闭开关中 声明(9.4.2)。在故障诊断语句之后执行的下一个语句应为 标记语句,其标签是同一switch语句的大小写标签或默认标签。节目是 如果没有这样的声明,则格式错误

[ Example:
void f(int n) {
void g(), h(), i();
switch (n) {
  case 1:
  case 2:
    g();
    [[fallthrough]];
  case 3: // warning on fallthrough discouraged
    h();
  case 4: // implementation may warn on fallthrough
    i();
    [[fallthrough]]; // ill-formed
  }
}
—end example ]

看。

我总是写一个
中断在每个
案例之前,如下所示:

switch(val) {
    break; case 0:
        foo();
    break; case 1:
        bar();
    break; case 2:
        baz();
    break; default:
        roomba();
}
这样,如果<代码>中断,对眼睛来说更加明显丢失。初始<代码>中断是多余的,但它有助于保持一致性


这是一个传统的
switch
语句,我只是以不同的方式使用了空格,删除了通常在
中断之后的换行符案例之前

这里有一个关于强迫性仇恨的答案

首先,
switch
语句是花哨的goto。它们可以与其他控制流相结合(著名的是),但这里明显的类比是一个goto或两个goto。下面是一个无用的例子:

switch (var) {
    CASE1: case 1:
        if (foo) goto END; //same as break
        goto CASE2; //same as fallthrough
    CASE2: case 2:
        break;
    CASE3: case 3:
        goto CASE2; //fall *up*
    CASE4: case 4:
        return; //no break, but also no fallthrough!
    DEFAULT: default:
        continue; //similar, if you're in a loop
}
END:
我推荐这个吗?不。事实上,如果你考虑这个只是为了说明一个失败,那么你的问题实际上是另外一个问题

这类代码确实非常清楚地表明,在案例1中可能发生故障,但正如其他位所示,这是一种非常强大的技术,通常也容易被滥用。使用时要小心

忘记一个
中断
?那么,您也会偶尔忘记您选择的任何注释。在更改switch语句时忘记说明故障?你是个糟糕的程序员。在修改switch语句(或者任何代码)时,首先需要理解它们


老实说,我很少犯这种错误(忘记了中断)——当然比我犯的其他“常见”编程错误(例如,严格的别名)要少。为了安全起见,我现在(并建议您)只需编写
//fallthrough
,因为这至少澄清了意图


除此之外,这只是程序员需要接受的现实。在编写代码后校对代码,并发现调试中偶尔出现的问题。这就是生活。

建议:如果你总是在case子句之间留出一条空行,那么浏览代码的人会更容易看到没有“break”:

switch (val) {
    case 0:
        foo();
        break;

    case 1:
        bar();

    case 2:
        baz();
        break;

    default:
        roomba();
}
当单个case子句中有大量代码时,这就没有那么有效了,但这本身往往是一种糟糕的代码味道。

您可以使用如下“python风格”开关(,可能让人惊讶的是这是 实际上,法律构造符合标准)

关键技巧是使整个switch语句变得简单 语句-如往常一样,存在范围块

相反,如果语句中穿插了
switch (val) {
    case 0:
        foo();
        break;

    case 1:
        bar();

    case 2:
        baz();
        break;

    default:
        roomba();
}
switch (a) if (false)
    case 4:
    case 5:
    case 6:
        std::cout << "4,5,6" << std::endl; //single statement
        else if (false)
    case 7:
    case 8:
        { //compound is ok too
            std::cout << "7,";
            std::cout << "8" << std::endl;
        }
        else if (false)
    default:
        std::cout << "default" << std::endl;
  #define FORCEBREAK else if(false)
#include <iostream>

void switchless(int a) {
    switch (a) if (false)
        case 4:
        case 5:
        case 6:
            std::cout << "4,5,6" << std::endl; //single statement
            else if (false)
        case 7:
        case 8:
            { //compound is ok too
                std::cout << "7,";
                std::cout << "8" << std::endl;
            }
            else if (false)
        default:
            std::cout << "default" << std::endl;
}

int main() {
    for (int i = 0; i < 10; ++i)
        switchless(i);
}
default
default
default
default
4,5,6
4,5,6
4,5,6
7,8
7,8
default