¿Cómo salir de un bucle desde el interior de un interruptor?

113

Estoy escribiendo un código que se parece a esto:

while(true) {
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        break; // **HERE, I want to break out of the loop itself**
    }
}

¿Hay alguna forma directa de hacerlo?

Sé que puedo usar una bandera y romper el ciclo poniendo un salto condicional justo después del cambio. Solo quiero saber si C ++ ya tiene alguna construcción para esto.

jrharshath
fuente
20
¿Por qué necesita un descanso condicional después del cambio? Simplemente cambie su tiempo de while (verdadero) a while (bandera) ...
Tal Pressman
7
@Dave_Jarvis Supongo que esta es una versión simplificada que ha puesto aquí para ilustrar lo que estaba tratando de hacer.
Alterlife
6
Si usted es uno de estos programadores que produce funciones que tienen varias páginas, encontrará gotoatractiva y, a veces, la única salida limpia. Si tiende a organizar su código en pequeñas funciones que tienen solo unas pocas líneas y hacen una sola cosa cada una, nunca se encontrará con este problema. (Por cierto, su código también será más fácil de leer.)
sbi
13
Si le apetece recibir un consejo para dejar de fumar cuando todo lo que quiere saber es cómo llegar a la estación de metro más cercana.
Michael Krelin - hacker
7
@hacker: Bueno, si no puede ver la estación de metro frente a usted debido a todo el humo, ese consejo podría no ser tan malo. :)
sbi

Respuestas:

49

Premisa

El siguiente código debe considerarse de mala forma, independientemente del idioma o la funcionalidad deseada:

while( true ) {
}

Argumentos de apoyo

El while( true )bucle es de mala forma porque:

  • Rompe el contrato implícito de un bucle while.
    • La declaración del ciclo while debe indicar explícitamente la única condición de salida.
  • Implica que se repite para siempre.
    • Se debe leer el código dentro del bucle para comprender la cláusula de terminación.
    • Los bucles que se repiten para siempre evitan que el usuario finalice el programa desde dentro del programa.
  • Es ineficaz.
    • Hay varias condiciones de terminación de bucle, incluida la comprobación de "verdadero".
  • Es propenso a los errores.
    • No se puede determinar fácilmente dónde colocar el código que siempre se ejecutará para cada iteración.
  • Conduce a un código innecesariamente complejo.
  • Análisis automático de código fuente.
    • Para encontrar errores, análisis de complejidad del programa, verificaciones de seguridad o derivar automáticamente cualquier otro comportamiento del código fuente sin la ejecución del código, especificar las condiciones iniciales de ruptura permite que los algoritmos determinen invariantes útiles, mejorando así las métricas de análisis automático del código fuente.
  • Bucles infinitos.
    • Si todo el mundo siempre usa while(true)bucles for que no son infinitos, perdemos la capacidad de comunicarnos de manera concisa cuando los bucles en realidad no tienen una condición de terminación. (Podría decirse que esto ya sucedió, por lo que el punto es discutible).

Alternativa a "Ir a"

El siguiente código es una mejor forma:

while( isValidState() ) {
  execute();
}

bool isValidState() {
  return msg->state != DONE;
}

Ventajas

Sin bandera. No se goto. Sin excepción. Fácil de cambiar. Fácil de leer. Fácil de arreglar. Además el código:

  1. Aísla el conocimiento de la carga de trabajo del ciclo del propio ciclo.
  2. Permite a alguien que mantiene el código ampliar fácilmente la funcionalidad.
  3. Permite que se asignen múltiples condiciones de terminación en un solo lugar.
  4. Separa la cláusula de terminación del código a ejecutar.
  5. Es más seguro para las centrales nucleares. ;-)

El segundo punto es importante. Sin saber cómo funciona el código, si alguien me pide que haga que el bucle principal deje que otros subprocesos (o procesos) tengan algo de tiempo de CPU, se me ocurren dos soluciones:

Opción 1

Inserte fácilmente la pausa:

while( isValidState() ) {
  execute();
  sleep();
}

Opcion 2

Anular ejecutar:

void execute() {
  super->execute();
  sleep();
}

Este código es más simple (por lo tanto, más fácil de leer) que un bucle con un archivo switch. El isValidStatemétodo solo debe determinar si el ciclo debe continuar. El caballo de batalla del método debe abstraerse en el executemétodo, lo que permite que las subclases anulen el comportamiento predeterminado (una tarea difícil con un switchy incorporado goto).

Ejemplo de Python

Compare la siguiente respuesta (a una pregunta de Python) que se publicó en StackOverflow:

  1. Bucle para siempre.
  2. Pídale al usuario que ingrese su elección.
  3. Si la entrada del usuario es 'reiniciar', continúa repitiendo para siempre.
  4. De lo contrario, deje de hacer bucles para siempre.
  5. Final.
Código
while True: 
    choice = raw_input('What do you want? ')

    if choice == 'restart':
        continue
    else:
        break

print 'Break!' 

Versus:

  1. Inicialice la elección del usuario.
  2. Bucle mientras la elección del usuario es la palabra 'reiniciar'.
  3. Pídale al usuario que ingrese su elección.
  4. Final.
Código
choice = 'restart';

while choice == 'restart': 
    choice = raw_input('What do you want? ')

print 'Break!'

Aquí, while Trueresulta en un código engañoso y demasiado complejo.

Dave Jarvis
fuente
45
La idea de que while(true)debería ser dañina me parece extraña. Hay bucles que se comprueban antes de ejecutarse, hay los que se comprueban después y hay los que se comprueban en el medio. Dado que este último no tiene una construcción sintáctica en C y C ++, tendrá que usar while(true)o for(;;). Si considera que esto es incorrecto, todavía no ha pensado lo suficiente en los diferentes tipos de bucles.
sbi
34
Razones por las que no estoy de acuerdo: while (verdadero) y for (;;;) no son confusos (a diferencia de do {} while (verdadero)) porque establecen lo que están haciendo desde el principio. En realidad, es más fácil buscar declaraciones de "ruptura" que analizar una condición de bucle arbitraria y buscar dónde está establecida, y no es más difícil probar la exactitud. El argumento sobre dónde colocar el código es igual de intratable en presencia de una instrucción continue, y no mucho mejor con declaraciones if. El argumento de la eficiencia es positivamente tonto.
David Thornley
23
Siempre que vea "while (verdadero)", puede pensar en un bucle que tiene condiciones de terminación internas, que no son más difíciles que las condiciones de final arbitrarias. Simplemente, un ciclo "while (foo)", donde foo es una variable booleana establecida de forma arbitraria, no es mejor que "while (true)" con saltos. Tienes que revisar el código en ambos casos. Por cierto, presentar una razón tonta (y la microeficiencia es un argumento tonto) no ayuda en su caso.
David Thornley
5
@Dave: Di una razón: es la única forma de obtener un bucle que verifique la condición en el medio. Ejemplo clásico: por while(true) {string line; std::getline(is,line); if(!is) break; lines.push_back(line);}supuesto, podría transformar esto en un bucle preacondicionado (usando std::getlinecomo condición de bucle), pero esto tiene desventajas por sí solo ( linetendría que ser una variable fuera del alcance del bucle). Y si lo hiciera, tendría que preguntar: ¿Por qué tenemos tres bucles de todos modos? Siempre podríamos transformar todo en un bucle preacondicionado. Utilice lo que mejor se adapte. Si while(true)encaja, úselo.
sbi
3
Como cortesía para las personas que leen esta respuesta, evitaría comentar sobre la calidad del bien que produjo la pregunta y me limitaría a responder la pregunta en sí. La pregunta es una variación de lo siguiente, que creo que es una característica de muchos lenguajes: tienes un bucle anidado dentro de otro bucle. Quieres salir del bucle exterior del bucle interior. ¿Cómo se hace esto?
Alex Leibowitz
172

Puede utilizar goto.

while ( ... ) {
   switch( ... ) {
     case ...:
         goto exit_loop;

   }
}
exit_loop: ;
Mehrdad Afshari
fuente
12
Simplemente no te vuelvas loco con esa palabra clave.
Fragsworth
45
Es gracioso que me voten negativamente porque a la gente no le gusta goto. El OP se menciona claramente sin usar banderas . ¿Puede sugerir una mejor manera, considerando la limitación del OP? :)
Mehrdad Afshari
18
votó a favor solo para compensar a los que odian ir a ir sin sentido. Supongo que Mehrdad sabía que iba a perder un par de puntos por sugerir una solución sensata aquí.
Michael Krelin - hacker
6
+1, no hay mejor manera en mi humilde opinión, incluso cuando se consideran las limitaciones de los OP. Las banderas son feas, rompen la lógica en todo el circuito y, por lo tanto, son más difíciles de entender.
Johannes Schaub - litb
11
al final del día, sin la construcción goto, todos los lenguajes fallan. los lenguajes de alto nivel lo ofuscan, pero si miras lo suficientemente bajo el capó, encontrarás que cada ciclo o condición se reduce a ramas condicionales e incondicionales. nos engañamos a nosotros mismos al usar las palabras clave for, while, until, switch, if, pero eventualmente todas utilizan GOTO (o JMP) de una forma u otra. puede engañarse a sí mismo, inventando todo tipo de formas inteligentes de ocultarlo, o puede simplemente ser pragmáticamente honesto y usar goto, cuando sea apropiado, y con moderación.
no sincronizado
58

Una solución alternativa es usar la palabra clave continueen combinación con break, es decir:

for (;;) {
    switch(msg->state) {
    case MSGTYPE
        // code
        continue; // continue with loop
    case DONE:
        break;
    }
    break;
}

Use la continuedeclaración para terminar cada etiqueta de caso donde desea que continúe el ciclo y use la breakdeclaración para terminar las etiquetas de caso que deberían terminar el ciclo.

Por supuesto, esta solución solo funciona si no hay código adicional para ejecutar después de la instrucción de cambio.

sakra
fuente
9
Si bien esto es realmente muy elegante, tiene la desventaja de que la mayoría de los desarrolladores primero tienen que mirarlo durante un minuto para comprender cómo funciona o si funciona. :(
sbi
8
La última oración es lo que hace que esto no sea tan bueno. :(De lo contrario, una implementación muy limpia.
Nawfal
Cuando lo piensas, ningún código de computadora es bueno. Solo estamos capacitados para comprenderlo. Por ejemplo, XML parecía basura hasta que lo aprendí. Ahora parece menos basura. for (int x = 0; x <10; ++ x, moveCursor (x, y)) en realidad causó un error lógico en mi programa. Olvidé que la condición de actualización ocurrió al final.
22

Una forma ordenada de hacer esto sería poner esto en una función:

int yourfunc() {

    while(true) {

        switch(msg->state) {
        case MSGTYPE: // ... 
            break;
        // ... more stuff ...
        case DONE:
            return; 
        }

    }
}

Opcionalmente (pero 'malas prácticas'): como ya se sugirió, podría usar un goto o lanzar una excepción dentro del interruptor.

Alterlife
fuente
4
La excepción debería usarse para, bueno, lanzar una excepción, no un comportamiento bien conocido de una función.
Clement Herreman
Estoy de acuerdo con Afterlife: Ponlo en una función.
dalle
14
Lanzar una excepción es realmente malo en este caso. Significa "Quiero usar un goto, pero leí en alguna parte que no debería usarlos, así que usaré un subterfugio y fingiré parecer inteligente".
Gorpik
Estoy de acuerdo en que lanzar una excepción o usar un goto es una idea terrible, pero son opciones que funcionan y se han enumerado como tales en mi respuesta.
Alterlife
2
Lanzar una excepción es sustituir un COMEFROM por un GOTO. No lo hagas. El goto funciona mucho mejor.
David Thornley
14

AFAIK no hay "doble ruptura" o construcción similar en C ++. El más cercano sería un goto, que, si bien tiene una mala connotación en su nombre, existe en el idioma por una razón, siempre que se use con cuidado y con moderación, es una opción viable.

Ámbar
fuente
8

Podría poner su interruptor en una función separada como esta:

bool myswitchfunction()
{
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        return false; // **HERE, I want to break out of the loop itself**
    }
    return true;
}

while(myswitchfunction())
    ;
Adam Pierce
fuente
7

Incluso si no le gusta goto, no use una excepción para salir de un bucle. El siguiente ejemplo muestra lo feo que podría ser:

try {
  while ( ... ) {
    switch( ... ) {
      case ...:
        throw 777; // I'm afraid of goto
     }
  }
}
catch ( int )
{
}

Usaría gotocomo en esta respuesta. En este caso goto, el código será más claro que cualquier otra opción. Espero que esta pregunta sea de ayuda.

Pero creo que usar gotoes la única opción aquí debido a la cadena while(true). Debería considerar la refactorización de su bucle. Supongo la siguiente solución:

bool end_loop = false;
while ( !end_loop ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        end_loop = true; break;
    }
}

O incluso lo siguiente:

while ( msg->state != DONE ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
}
Kirill V. Lyadvinsky
fuente
1
Sí, pero me gustan menos las excepciones ;-)
quamrana
3
¡Usar una excepción para emular un goto es, por supuesto, peor que usar un goto! Probablemente también sea significativamente más lento.
John Carter
2
@Downvoter: ¿Es porque digo que no debería usar la excepción, o porque sugiero refactorizar?
Kirill V. Lyadvinsky
1
No el votante en contra, pero ... Su respuesta no está clara si está proponiendo o condenando la idea de usar una excepción para salir del círculo. Quizás la primera oración debería ser: "Incluso si no le gusta goto, no use una excepción para salir de un bucle:"?
Jonathan Leffler
1
@Jonathan Leffler, Gracias, mostró la muestra de valioso comentario. actualizó la respuesta teniendo en cuenta sus comentarios.
Kirill V. Lyadvinsky
5

No hay una construcción de C ++ para salir del ciclo en este caso.

Use una bandera para interrumpir el ciclo o (si corresponde) extraiga su código en una función y use return.

diente filoso
fuente
2

Potencialmente, podría usar goto, pero preferiría establecer una bandera que detenga el bucle. Luego salga del interruptor.

Martin York
fuente
2

¿Por qué no arreglar la condición en su bucle while, haciendo que el problema desaparezca?

while(msg->state != DONE)
{
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        // We can't get here, but for completeness we list it.
        break; // **HERE, I want to break out of the loop itself**
    }
}
Marca B
fuente
2

No, C ++ no tiene una construcción para esto, dado que la palabra clave "break" ya está reservada para salir del bloque de interruptores. Alternativamente, un do.. while () con una bandera de salida podría ser suficiente.

do { 
    switch(option){
        case 1: ..; break;
        ...
        case n: .. ;break;
        default: flag = false; break;
    }
} while(flag);
Prashanth Sriram
fuente
1

Yo creo que;

while(msg->state != mExit) 
{
    switch(msg->state) 
    {
      case MSGTYPE: // ...
         break;
      case DONE:
      //  .. 
      //  ..
      msg->state =mExit;
      break;
    }
}
if (msg->state ==mExit)
     msg->state =DONE;
ercan
fuente
0

La forma más sencilla de hacerlo es poner un IF simple antes de hacer el SWITCH, y que IF pruebe su condición para salir del bucle ... tan simple como puede ser

SpiriAd
fuente
2
Um ... podrías ser aún más simple poniendo esa condición en el argumento while. ¡Quiero decir, para eso está ahí! :)
Mark A. Donohoe
0

La breakpalabra clave en C ++ solo termina la iteración o switchdeclaración adjunta más anidada . Por lo tanto, no podría salir del while (true)bucle directamente dentro de la switchdeclaración; sin embargo, podría usar el siguiente código, que creo que es un patrón excelente para este tipo de problema:

for (; msg->state != DONE; msg = next_message()) {
    switch (msg->state) {
    case MSGTYPE:
        //...
        break;

    //...
    }
}

Si necesita hacer algo cuando msg->statees igual DONE(como ejecutar una rutina de limpieza), coloque ese código inmediatamente después del forciclo; es decir, si actualmente tiene:

while (true) {
    switch (msg->state) {
    case MSGTYPE:
        //... 
        break;

    //...

    case DONE:
        do_cleanup();
        break;
    }

    if (msg->state == DONE)
        break;

    msg = next_message();
}

Luego usa en su lugar:

for (; msg->state != DONE; msg = next_message()) {
    switch (msg->state) {
    case MSGTYPE:
        //...
        break;

    //...
    }
}

assert(msg->state == DONE);
do_cleanup();
Daniel Trebbien
fuente
0

Me sorprende lo simple que es esto considerando la profundidad de las explicaciones ... Aquí está todo lo que necesita ...

bool imLoopin = true;

while(imLoopin) {

    switch(msg->state) {

        case MSGTYPE: // ... 
            break;

        // ... more stuff ...

        case DONE:
            imLoopin = false;
            break;

    }

}

LOL !! ¡De Verdad! ¡Eso es todo lo que necesitas! ¡Una variable extra!

Mark A. Donohoe
fuente
0
while(MyCondition) {
switch(msg->state) {
case MSGTYPE: // ... 
    break;
// ... more stuff ...
case DONE:
   MyCondition=false; // just add this code and you will be out of loop.
    break; // **HERE, you want to break out of the loop itself**
}
}
Wajeed-MSFT
fuente
0

Tengo el mismo problema y lo resolví usando una bandera.

bool flag = false;
while(true) {
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        flag = true; // **HERE, I want to break out of the loop itself**
    }
    if(flag) break;
}
Ram Suthar
fuente
-2

Si recuerdo bien la sintaxis de C ++, puede agregar una etiqueta a las breakdeclaraciones, como para goto. Entonces, lo que desea se escribiría fácilmente:

while(true) {
    switch(msg->state) {
    case MSGTYPE: // ...
        break;
    // ... more stuff ...
    case DONE:
        break outofloop; // **HERE, I want to break out of the loop itself**
    }
}

outofloop:
// rest of your code here
Andrei Vajna II
fuente
1
Desafortunadamente, tu memoria es defectuosa. Sería una buena característica, en esas raras ocasiones en las que sería útil. (He visto propuestas sobre el número de niveles para romper, pero me parecen errores a la espera de suceder)
David Thornley
Entonces debe haber sido Java. Gracias por responder. Y, por supuesto, también tienes razón con el resto.
Andrei Vajna II
-3
  while(true)
  {
    switch(x)
    {
     case 1:
     {
      break;
     }
    break;
   case 2:
    //some code here
   break;
  default:
  //some code here
  }
}
Chevaughn
fuente
1
Todas las interrupciones en esa muestra de código se salen de la declaración de cambio.
Dan Hulme