program - switch syntax c++
C++ Fuerza el error/advertencia en tiempo de compilación en la caída implícita en el conmutador (4)
Aquí hay una respuesta al odio compulsivo.
Primero, las declaraciones de
switch
son fantasías fantasiosas.
Se pueden combinar con otro flujo de control (famoso,
el dispositivo de Duff
), pero la analogía obvia aquí es un goto o dos.
Aquí hay un ejemplo inútil:
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:
¿Recomiendo esto? No. De hecho, si está considerando esto solo para anotar una falla, entonces su problema es en realidad otra cosa.
Este tipo de código deja muy en claro que puede ocurrir una falla en el caso 1, pero como muestran los otros bits, esta es una técnica muy poderosa en general que también se presta al abuso. Ten cuidado si lo usas.
¿Olvidando un
break
?
Bueno, entonces ocasionalmente también olvidarás cualquier anotación que elijas.
¿Olvidar tener en cuenta la falla al cambiar una declaración de cambio?
Eres un mal programador.
Al modificar las declaraciones de cambio
(o realmente
cualquier
código)
, primero debe
comprenderlas
.
Honestamente, rara vez cometo este tipo de error (olvidando un descanso), ciertamente menos que otros errores de programación "comunes" (alias estricto, por ejemplo).
Para estar seguro, actualmente lo hago (y recomiendo que lo hagas) simplemente escribo
//fallthrough
, ya que esto al menos aclara la intención.
Aparte de eso, es una realidad que los programadores deben aceptar. Revise su código después de escribirlo y encuentre el problema ocasional con la depuración. Así es la vida.
switch
declaraciones de
switch
pueden ser súper útiles, pero conducen a un error común en el que un programador olvidó una declaración de interrupción:
switch(val) {
case 0:
foo();
break;
case 1:
bar();
// oops
case 2:
baz();
break;
default:
roomba();
}
Obviamente, no recibirá una advertencia, ya que a veces se desea explícitamente la falla. Un buen estilo de codificación sugiere comentar cuando su caída es deliberada, pero a veces eso es insuficiente.
Estoy bastante seguro de que la respuesta a esta pregunta es no, pero: ¿hay alguna forma actualmente (o propuesta en el futuro) para poder pedirle al compilador que arroje un error (o al menos una advertencia!) Si su
case
hace no tener al menos uno de
break;
o algo en el sentido de
// fallthru
?
Sería bueno tener una opción de programación defensiva para usar declaraciones de
switch
.
Bueno, clang tiene
-Wimplicit-fallthrough
que no conocía pero encontré usando
-Weverything
.
Entonces, para este código, me da la siguiente advertencia (
verlo en vivo
):
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;
La única documentación que puedo encontrar para este indicador está en la Referencia de atributos que dice:
El atributo clang :: fallthrough se usa junto con el argumento -Wimplicit-fallthrough para anotar la caída intencional entre etiquetas de conmutador. Solo se puede aplicar a una declaración nula colocada en un punto de ejecución entre cualquier declaración y la siguiente etiqueta de cambio. Es común marcar estos lugares con un comentario específico, pero este atributo está destinado a reemplazar los comentarios con una anotación más estricta, que el compilador puede verificar.
y proporciona un ejemplo de cómo marcar una caída explícita:
case 44: // warning: unannotated fall-through
g();
[[clang::fallthrough]];
case 55: // no warning
Este uso de un
attribute
para marcar fallos explícitos tiene la desventaja de no ser portátil.
Visual Studio
genera un error y
gcc
genera la siguiente advertencia:
warning: attributes at the beginning of statement are ignored [-Wattributes]
lo cual es un problema si quieres usar
-Werror
.
Intenté esto con
gcc 4.9
y parece que
gcc
no admite esta advertencia:
error: opción de línea de comando no reconocida ''-Wimplicit-fallthrough''
A partir de
GCC 7
, se
-Wimplicit-fallthrough
y
__attribute__((fallthrough))
se puede utilizar para suprimir las advertencias cuando el fallthrough es intencional.
GCC reconoce los comentarios "fallidos" en ciertos escenarios, pero puede confundirse con
bastante facilidad
.
No veo una forma de generar tal advertencia para
Visual Studio
.
Tenga en cuenta que
Chandler Carruth
explica que
-Weverything
no es para uso en producción:
Este es un grupo de locos que literalmente habilita todas las advertencias en Clang. No uses esto en tu código. Está destinado estrictamente a los desarrolladores de Clang o para explorar qué advertencias existen.
pero es útil para descubrir qué advertencias existen.
C ++ 17 cambios
En C ++ 17 obtenemos el atributo [[fallthrough]] cubierto en [dcl.attr.fallthrough]p1 :
El error de token de atributo se puede aplicar a una declaración nula (9.2); tal declaración es una declaración fallida. El fallo de token de atributo aparecerá como máximo una vez en cada lista de atributos y no deberá estar presente ninguna cláusula de argumento de atributo. Una declaración fallida solo puede aparecer dentro de una declaración de interruptor adjunta (9.4.2). La siguiente declaración que se ejecute después de una declaración de caída será una declaración etiquetada cuya etiqueta es una etiqueta de caso o etiqueta predeterminada para la misma declaración de cambio. El programa está mal formado si no hay tal declaración.
...
[ 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 ]
Consejo: si constantemente coloca una línea en blanco entre las cláusulas del caso, la ausencia de un ''salto'' se vuelve más visible para un humano que descrema el código:
switch (val) {
case 0:
foo();
break;
case 1:
bar();
case 2:
baz();
break;
default:
roomba();
}
Esto no es tan efectivo cuando hay mucho código dentro de las cláusulas de casos individuales, pero eso tiende a ser un mal olor de código en sí mismo.
Siempre escribo un
break;
antes de cada
case
, como sigue:
switch(val) {
break; case 0:
foo();
break; case 1:
bar();
break; case 2:
baz();
break; default:
roomba();
}
De esta manera, es mucho más obvio a la vista si hay un
break;
Está perdido.
El
break;
inicial
break;
Supongo que es redundante, pero ayuda a ser coherente.
Esta es una declaración de
switch
convencional, simplemente he usado espacios en blanco de una manera diferente, eliminando la nueva línea que normalmente es después de un
break;
y antes del próximo
case
.