switch
语句可能非常有用,但会导致程序员忘记中断语句的常见错误:
switch(val) {
case 0:
foo();
break;
case 1:
bar();
// oops
case 2:
baz();
break;
default:
roomba();
}
你很明显不会收到警告,因为有时明确需要通过。良好的编码风格建议在你的堕落是故意的时候发表评论,但有时这是不够的。
我很确定这个问题的答案是否定的,但是:目前(或将来提出的)是否能够让编译器抛出错误(或者至少是警告!)您的case
至少没有break;
中的一个或// fallthru
的效果?使用switch
语句有一个防御性编程选项会很好。
答案 0 :(得分:67)
铿锵有-Wimplicit-fallthrough
我不知道但是通过使用-Weverything
找到了。因此,对于此代码,它会向我发出以下警告( see it live ):
warning: unannotated fall-through between switch labels [-Wimplicit-fallthrough]
case 2:
^
note: insert '[[clang::fallthrough]];' to silence this warning
case 2:
^
[[clang::fallthrough]];
note: insert 'break;' to avoid fall-through
case 2:
^
break;
我可以找到这个标志的唯一文件在Attribute Reference中,其中包含:
clang :: fallthrough属性与。一起使用 - 用于注释切换标签之间故意掉落的轻微 - 通过秋季参数。它只能应用于null语句 放在任何语句和下一个语句之间的执行点 开关标签。通常用特定的标记来标记这些地方 注释,但此属性旨在用更多注释替换注释 严格注释,可由编译器检查。
并提供了如何标记显式直通的示例:
case 44: // warning: unannotated fall-through
g();
[[clang::fallthrough]];
case 55: // no warning
使用attribute来标记明确的堕落具有不可移植的缺点。 Visual Studio
生成错误,gcc
生成以下警告:
warning: attributes at the beginning of statement are ignored [-Wattributes]
如果您想使用-Werror
,则会出现问题。
我使用gcc 4.9
尝试了此操作,看起来gcc
不支持此警告:
错误:无法识别的命令行选项'-Wimplicit-fallthrough'
从GCC 7开始,-Wimplicit-fallthrough
支持__attribute__((fallthrough))
,Visual Studio
可用于在故意触发故障时抑制警告。海湾合作委员会确实在某些情况下承认“通过”评论,但可能会混淆fairly easily。
我没有看到为-Weverything
生成此类警告的方法。
注意,Chandler Carruth说明[ 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 ]
不适合生产使用:
这是一个疯狂的团体,从字面上实现了Clang的每一个警告。 不要在代码中使用它。它主要用于Clang 开发人员或探索存在的警告。
但它有助于弄清楚存在什么警告。
在C ++ 17中,我们得到[dcl.attr.fallthrough]p1中涵盖的属性 [[fallthrough]] :
属性标记可以应用于null语句(9.2);这样的声明是一个突破 声明。属性 - 标记通过在每个属性列表中最多出现一次,并且没有属性参数 - 条款应在场。 fallthrough语句可能只出现在封闭的开关中 声明(9.4.2)。在fallthrough语句之后执行的下一个语句应为a 带标签的语句,其标签是同一switch语句的case标签或default标签。该计划是 如果没有这样的陈述,就会形成错误。
...
{{1}}
答案 1 :(得分:12)
我总是在每个break;
之前写一个case
,如下所示:
switch(val) {
break; case 0:
foo();
break; case 1:
bar();
break; case 2:
baz();
break; default:
roomba();
}
这样,如果缺少break;
,则更明显。我认为最初的break;
是多余的,但它有助于保持一致。
这是一个传统的switch
语句,我只是以不同的方式使用空格,删除通常在break;
之后和下一个case
之前的换行符。
答案 2 :(得分:6)
建议:如果你一直在case case子句之间添加一个空行,那么人们在查看代码时就会看到没有“break”:
switch (val) {
case 0:
foo();
break;
case 1:
bar();
case 2:
baz();
break;
default:
roomba();
}
当个别case子句中有很多代码时,这并不是那么有效,但这本身往往是一个糟糕的代码味道。
答案 3 :(得分:1)
这是强迫性仇恨的答案。
首先,switch
语句是花哨的。它们可以与其他控制流程结合起来(着名的是Duff's Device),但这里显而易见的类比是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中可能发生漏洞,但正如其他位所示,这是一种非常强大的技术一般而言,这也有助于滥用。如果使用它,请小心。
忘记break
?那么,你偶尔也会忘记你选择的任何注释。在更改switch语句时忘记考虑掉落?你是一个糟糕的程序员。在修改switch语句(或实际任何代码)时,您需要先理解它们。
老实说,我很少犯这种错误(忘了休息) - 肯定比我做的其他"普通"编程错误(例如严格别名)。为了安全起见,我现在(并建议你这样做)只写//fallthrough
,因为这至少说明了意图。
除此之外,它是程序员需要接受的一个现实。在编写代码后校对代码并发现调试时偶尔出现的问题。这就是生命。
答案 4 :(得分:0)
您可以使用“python 样式”开关,如下所示(see it live,也许令人惊讶的是,这是 实际上符合标准的合法结构)。
关键技巧是让整个 switch 语句变得简单 声明 - 即像往常一样有作用域块。
相反,我们开始一个链式 if 语句,其中散布着 case
标签。
if 语句本身无法访问,但您可以使用 else if(/*irrelevant*/)
链接以永不失败的方式继续切换。
else
很好地排除了其他分支。实际上它是一个“开关树”
现在,带有绕过条件的标签。
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)
突出意思。
缺点:
[[fallthrough]]
大多只是更好-Wno-switch-unreachable
在没有警告的情况下接受它)优点:
switch
语句的灵活性#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