C++ Force compile-time error/warning on implicit fall-through in switch
C++ Force compile-time error/warning on implicit fall-through in switch
switch
语句可能非常有用,但会导致程序员忘记 break 语句的常见错误:
switch(val) {
case 0:
foo();
break;
case 1:
bar();
// oops
case 2:
baz();
break;
default:
roomba();
}
您显然不会收到警告,因为有时明确需要失败。良好的编码风格建议在你故意失败时发表评论,但有时这还不够。
我很确定这个问题的答案是否定的,但是:目前(或将来提出)是否有任何方法能够要求编译器抛出错误(或至少是警告! ) 如果您的 case
至少没有 break;
之一或类似 // fallthru
的东西?如果能有一个使用 switch
语句的防御性编程选项就好了。
嗯,clang 有 -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,它说:
The clang::fallthrough attribute is used along with the
-Wimplicit-fallthrough argument to annotate intentional fall-through between switch labels. It can only be applied to a null statement
placed at a point of execution between any statement and the next
switch label. It is common to mark these places with a specific
comment, but this attribute is meant to replace comments with a more
strict annotation, which can be checked by the compiler.
并提供了如何标记显式失败的示例:
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
不支持这个警告:
error: unrecognized command line option '-Wimplicit-fallthrough'
截至 GCC 7, -Wimplicit-fallthrough
is supported and __attribute__((fallthrough))
can be used to suppress the warnings when fallthrough is intentional. GCC does recognize "fallthrough" comments in certain scenarios, but it can be confused fairly easily。
我没有看到为 Visual Studio
生成此类警告的方法。
注意,Chandler Carruth 说明 -Weverything
不用于生产:
This is an insane group that literally enables every warning in Clang.
Don't use this on your code. It is intended strictly for Clang
developers or for exploring what warnings exist.
但它有助于找出存在的警告。
C++17 变化
在 C++17 中,我们得到 [[fallthrough]] 中包含的属性 [dcl.attr.fallthrough]p1:
The attribute-token fallthrough may be applied to a null statement (9.2); such a statement is a fallthrough
statement. The attribute-token fallthrough shall appear at most once in each attribute-list and no attributeargument-
clause shall be present. A fallthrough statement may only appear within an enclosing switch
statement (9.4.2). The next statement that would be executed after a fallthrough statement shall be a
labeled statement whose label is a case label or default label for the same switch statement. The program is
ill-formed if there is no such statement.
...
[ 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 ]
我总是在每个case
前写一个break;
,如下:
switch(val) {
break; case 0:
foo();
break; case 1:
bar();
break; case 2:
baz();
break; default:
roomba();
}
这样一来,如果 break;
缺失,就更明显了。我想最初的 break;
是多余的,但它有助于保持一致。
这是一个常规的 switch
语句,我只是以不同的方式使用了空格,删除了通常在 break;
之后和下一个 case
之前的换行符。
这是对强迫性仇恨的回答。
首先,switch
语句是花哨的 goto。它们可以与其他控制流(著名的 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 语句时忘记考虑 fall-through?你是一个糟糕的程序员。在修改 switch 语句(或真正任何代码)时,您需要先理解它们。
老实说,我很少犯这种错误(忘记休息)——肯定比我犯其他 "common" 编程错误(例如,严格的别名)少。为了安全起见,我目前(并建议您这样做)只写 //fallthrough
,因为这至少阐明了意图。
除此之外,这只是程序员需要接受的现实。编写代码后校对代码,发现调试时偶尔出现的问题。这就是生活。
建议:如果您始终在 case 子句之间放置一个空行,那么 'break' 的缺失对于略读代码的人来说会变得更加明显:
switch (val) {
case 0:
foo();
break;
case 1:
bar();
case 2:
baz();
break;
default:
roomba();
}
当单个 case 子句中有很多代码时,这不是那么有效,但它本身往往是一种糟糕的代码味道。
您可以使用“python 样式”开关,如下所示(查看
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)
突出意义。
缺点:
- 最后一个方面有点突出了一个缺陷:它确实需要更多的工作,而且 C++17
[[fallthrough]]
基本上只是更好
- 您现在需要关闭编译器给出的无法访问的代码警告
(例如 GCC 要求
-Wno-switch-unreachable
在没有警告的情况下接受它)
优点:
- 很花哨
- 它讲授
switch
语句的灵活性
- 它确实强制人们正确地打破他们的 switch case,否则它将因编译时错误而失败
- 它也适用于 C with minor modifications
#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
switch
语句可能非常有用,但会导致程序员忘记 break 语句的常见错误:
switch(val) {
case 0:
foo();
break;
case 1:
bar();
// oops
case 2:
baz();
break;
default:
roomba();
}
您显然不会收到警告,因为有时明确需要失败。良好的编码风格建议在你故意失败时发表评论,但有时这还不够。
我很确定这个问题的答案是否定的,但是:目前(或将来提出)是否有任何方法能够要求编译器抛出错误(或至少是警告! ) 如果您的 case
至少没有 break;
之一或类似 // fallthru
的东西?如果能有一个使用 switch
语句的防御性编程选项就好了。
嗯,clang 有 -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,它说:
The clang::fallthrough attribute is used along with the -Wimplicit-fallthrough argument to annotate intentional fall-through between switch labels. It can only be applied to a null statement placed at a point of execution between any statement and the next switch label. It is common to mark these places with a specific comment, but this attribute is meant to replace comments with a more strict annotation, which can be checked by the compiler.
并提供了如何标记显式失败的示例:
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
不支持这个警告:
error: unrecognized command line option '-Wimplicit-fallthrough'
截至 GCC 7, -Wimplicit-fallthrough
is supported and __attribute__((fallthrough))
can be used to suppress the warnings when fallthrough is intentional. GCC does recognize "fallthrough" comments in certain scenarios, but it can be confused fairly easily。
我没有看到为 Visual Studio
生成此类警告的方法。
注意,Chandler Carruth 说明 -Weverything
不用于生产:
This is an insane group that literally enables every warning in Clang. Don't use this on your code. It is intended strictly for Clang developers or for exploring what warnings exist.
但它有助于找出存在的警告。
C++17 变化
在 C++17 中,我们得到 [[fallthrough]] 中包含的属性 [dcl.attr.fallthrough]p1:
The attribute-token fallthrough may be applied to a null statement (9.2); such a statement is a fallthrough statement. The attribute-token fallthrough shall appear at most once in each attribute-list and no attributeargument- clause shall be present. A fallthrough statement may only appear within an enclosing switch statement (9.4.2). The next statement that would be executed after a fallthrough statement shall be a labeled statement whose label is a case label or default label for the same switch statement. The program is ill-formed if there is no such statement.
...
[ 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 ]
我总是在每个case
前写一个break;
,如下:
switch(val) {
break; case 0:
foo();
break; case 1:
bar();
break; case 2:
baz();
break; default:
roomba();
}
这样一来,如果 break;
缺失,就更明显了。我想最初的 break;
是多余的,但它有助于保持一致。
这是一个常规的 switch
语句,我只是以不同的方式使用了空格,删除了通常在 break;
之后和下一个 case
之前的换行符。
这是对强迫性仇恨的回答。
首先,switch
语句是花哨的 goto。它们可以与其他控制流(著名的 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 语句时忘记考虑 fall-through?你是一个糟糕的程序员。在修改 switch 语句(或真正任何代码)时,您需要先理解它们。
老实说,我很少犯这种错误(忘记休息)——肯定比我犯其他 "common" 编程错误(例如,严格的别名)少。为了安全起见,我目前(并建议您这样做)只写 //fallthrough
,因为这至少阐明了意图。
除此之外,这只是程序员需要接受的现实。编写代码后校对代码,发现调试时偶尔出现的问题。这就是生活。
建议:如果您始终在 case 子句之间放置一个空行,那么 'break' 的缺失对于略读代码的人来说会变得更加明显:
switch (val) {
case 0:
foo();
break;
case 1:
bar();
case 2:
baz();
break;
default:
roomba();
}
当单个 case 子句中有很多代码时,这不是那么有效,但它本身往往是一种糟糕的代码味道。
您可以使用“python 样式”开关,如下所示(查看 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)
突出意义。
缺点:
- 最后一个方面有点突出了一个缺陷:它确实需要更多的工作,而且 C++17
[[fallthrough]]
基本上只是更好 - 您现在需要关闭编译器给出的无法访问的代码警告
(例如 GCC 要求
-Wno-switch-unreachable
在没有警告的情况下接受它)
优点:
- 很花哨
- 它讲授
switch
语句的灵活性 - 它确实强制人们正确地打破他们的 switch case,否则它将因编译时错误而失败
- 它也适用于 C with minor modifications
#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