web-dev-qa-db-fra.com

Comment sortir d'une boucle de l'intérieur d'un commutateur?

J'écris un code qui ressemble à ceci:

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

Y a-t-il un moyen direct de le faire?

Je sais que je peux utiliser un drapeau et rompre la boucle en mettant une rupture conditionnelle juste après le changement. Je veux juste savoir si C++ a déjà une construction pour cela.

93
jrharshath

Prémisse

Le code suivant doit être considéré comme une mauvaise forme, quelle que soit la langue ou la fonctionnalité souhaitée:

while( true ) {
}

Arguments à l'appui

La boucle while( true ) est de mauvaise forme car elle:

  • Brise le contrat implicite d'une boucle while.
    • La déclaration de la boucle while doit explicitement énoncer la condition only exit.
  • Cela implique qu'il boucle pour toujours.
    • Le code dans la boucle doit être lu pour comprendre la clause de terminaison.
    • Les boucles qui se répètent à jamais empêchent l'utilisateur de terminer le programme à partir de celui-ci.
  • Est inefficace.
    • Il existe plusieurs conditions de terminaison de boucle, y compris la vérification de "true".
  • Est sujet aux insectes.
    • Impossible de déterminer facilement où placer le code qui sera toujours exécuté à chaque itération.
  • Conduit à un code inutilement complexe.
  • Analyse automatique du code source.
    • Pour rechercher des bogues, une analyse de complexité de programme, des contrôles de sécurité ou dériver automatiquement tout autre comportement de code source sans exécution de code, la spécification de la ou des conditions de rupture initiales permet aux algorithmes de déterminer des invariants utiles, améliorant ainsi les mesures d'analyse automatique du code source.
  • Boucles infinies.
    • Si tout le monde utilise toujours while(true) pour des boucles qui ne sont pas infinies, nous perdons la capacité de communiquer de manière concise lorsque les boucles n'ont en réalité aucune condition de fin. (On peut soutenir que cela est déjà arrivé, alors le problème est sans objet.)

Alternative à "Go To"

Le code suivant est de meilleure forme:

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

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

Avantages

Pas de drapeau. Non goto. Pas exception. Facile à changer. Facile à lire. Facile à réparer. De plus le code:

  1. Isole la connaissance de la charge de travail de la boucle par rapport à la boucle elle-même.
  2. Permet à quelqu'un qui gère le code d'étendre facilement les fonctionnalités.
  3. Permet d'attribuer plusieurs conditions de terminaison en un seul endroit.
  4. Sépare la clause de terminaison du code à exécuter.
  5. Est plus sûr pour les centrales nucléaires. ;-)

Le deuxième point est important. Sans savoir comment le code fonctionne, si quelqu'un me demandait de faire en sorte que la boucle principale laisse un peu de temps processeur aux autres threads (ou processus), deux solutions s'imposent:

Option 1

Facilement insérer la pause:

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

Option 2

Remplacer exécuter:

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

Ce code est plus simple (donc plus facile à lire) qu’une boucle avec une switch incorporée. La méthode isValidState doit uniquement déterminer si la boucle doit continuer. Le cheval de travail de la méthode doit être résumé dans la méthode execute, qui permet aux sous-classes de remplacer le comportement par défaut (tâche difficile utilisant les variables incorporées switch et goto).

Exemple Python

Comparez la réponse suivante (à une question Python) postée sur StackOverflow:

  1. Boucle pour toujours.
  2. Demandez à l'utilisateur de saisir son choix.
  3. Si l'entrée de l'utilisateur est 'redémarrer', continuez à boucler pour toujours.
  4. Sinon, arrêtez de boucler pour toujours.
  5. Fin.
while True: 
    choice = raw_input('What do you want? ')

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

print 'Break!' 

Contre:

  1. Initialiser le choix de l'utilisateur.
  2. Boucle pendant que le choix de l'utilisateur est le mot "redémarrer".
  3. Demandez à l'utilisateur de saisir son choix.
  4. Fin.
choice = 'restart';

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

print 'Break!'

Ici, while True génère un code trompeur et trop complexe.

52
Dave Jarvis

Vous pouvez utiliser goto.

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

   }
}
exit_loop: ;
144
Mehrdad Afshari

Une autre solution consiste à utiliser le mot clé continue en combinaison avec break, c'est-à-dire:

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

Utilisez l'instruction continue pour terminer chaque étiquette de casse dans laquelle vous souhaitez que la boucle continue et utilisez l'instruction break pour terminer les étiquettes de casse devant terminer la boucle.

Bien entendu, cette solution ne fonctionne que s’il n’ya pas de code supplémentaire à exécuter après l’instruction switch.

49
sakra

Une façon astucieuse de le faire serait de le mettre dans une fonction:

int yourfunc() {

    while(true) {

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

    }
}

Facultatif (mais «mauvaises pratiques»): comme déjà suggéré, vous pouvez utiliser un goto ou envoyer une exception à l'intérieur du commutateur.

20
Alterlife

Autant que je sache, il n'y a pas de "double rupture" ou de construction similaire en C++. Le plus proche serait un goto - qui, bien qu'il ait une mauvaise connotation dans son nom, existe dans le langage pour une raison - tant qu'il est utilisé avec précaution et avec parcimonie, c'est une option viable.

13
Amber

Vous pouvez placer votre commutateur dans une fonction séparée comme celle-ci:

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())
    ;
8
Adam Pierce

Même si vous n'aimez pas aller à goto, n'utilisez pas d'exception pour sortir d'une boucle. L'exemple suivant montre à quel point cela pourrait être moche:

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

J'utiliserais goto comme dans this answer. Dans ce cas, goto rendra le code plus clair que toute autre option. J'espère que cette question sera utile.

Mais je pense que l’utilisation de goto est la seule option ici à cause de la chaîne while(true). Vous devriez envisager de refactoriser votre boucle. Je suppose que la solution suivante:

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

Ou même les suivantes:

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

Dans ce cas, il n'y a pas de construction C++ pour sortir de la boucle.

Utilisez un indicateur pour interrompre la boucle ou, le cas échéant, extrayez votre code dans une fonction et utilisez return.

5
sharptooth

Vous pouvez potentiellement utiliser goto, mais je préférerais définir un indicateur qui arrête la boucle. Puis sortez de l'interrupteur.

2
Martin York

Non, C++ n'a pas de construction pour cela, étant donné que le mot clé "break" est déjà réservé pour quitter le bloc de commutation. Alternativement, un do..while () avec un drapeau de sortie pourrait suffire.

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

Pourquoi ne pas simplement corriger la condition dans votre boucle while, provoquant la disparition du problème?

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**
    }
}
2
Mark B

Je pense;

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

Je suis étonné de voir à quel point cela est simple compte tenu de la profondeur des explications ... Voici tout ce dont vous avez besoin ...

bool imLoopin = true;

while(imLoopin) {

    switch(msg->state) {

        case MSGTYPE: // ... 
            break;

        // ... more stuff ...

        case DONE:
            imLoopin = false;
            break;

    }

}

LOL !! Vraiment! C'est tout ce dont vous avez besoin! Une variable supplémentaire!

0
MarqueIV

La façon la plus simple de le faire est de mettre un simple IF avant de faire le SWITCH, et que le SI teste votre condition pour sortir de la boucle .......... aussi simple que possible.

0
SpiriAd

Le mot clé break en C++ termine uniquement l'itération ou l'instruction switch la plus imbriquée. Ainsi, vous ne pouvez pas sortir de la boucle while (true) directement dans l'instruction switch; Cependant, vous pouvez utiliser le code suivant, ce qui, à mon avis, est un excellent modèle pour ce type de problème:

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

    //...
    }
}

Si vous avez besoin de faire quelque chose lorsque msg->state est égal à DONE (comme exécuter une routine de nettoyage), placez ce code immédiatement après la boucle for; c'est-à-dire si vous avez actuellement:

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

    //...

    case DONE:
        do_cleanup();
        break;
    }

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

    msg = next_message();
}

Puis utilisez plutôt:

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

    //...
    }
}

assert(msg->state == DONE);
do_cleanup();
0
Daniel Trebbien
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**
}
}
0
01010111 01010011

J'ai eu le même problème et résolu en utilisant un drapeau.

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;
}
0
Ram Suthar