Comment sortir d'une boucle depuis l'intérieur d'un interrupteur?

113

J'écris du 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 interruption conditionnelle juste après le commutateur. Je veux juste savoir si C ++ a déjà une construction pour cela.

jrharshath
la source
20
Pourquoi avez-vous besoin d'une pause conditionnelle après le changement? Changez simplement votre temps de while (true) à while (flag) ...
Tal Pressman
7
@Dave_Jarvis Je suppose que c'est une version simplifiée qu'il a mise ici pour illustrer ce qu'il essayait de faire.
Alterlife le
6
Si vous êtes l'un de ces programmeurs qui produisent des fonctions de plusieurs pages, vous trouverez gotoattrayant et, parfois, la seule solution propre. Si vous avez tendance à organiser votre code en petites fonctions qui ne font que quelques lignes et font une seule chose chacune, vous ne rencontrerez jamais ce problème. (Incidemment, votre code sera également plus facile à lire.)
sbi
13
Si vous avez envie de recevoir un conseil pour arrêter de fumer, tout ce que vous voulez savoir, c'est comment vous rendre à la station de métro la plus proche.
Michael Krelin - hacker le
7
@hacker: Eh bien, si vous ne pouvez pas voir la station de métro devant vous à cause de toute la fumée, ce conseil n'est peut-être pas si mauvais. :)
sbi

Réponses:

49

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 while( true )boucle est de mauvaise forme car elle:

  • Rompre le contrat implicite d'une boucle while.
    • La déclaration de boucle while doit explicitement indiquer la seule condition de sortie.
  • Implique qu'il boucle pour toujours.
    • Le code dans la boucle doit être lu pour comprendre la clause de fin.
    • Les boucles qui se répètent indéfiniment empêchent l'utilisateur de terminer le programme à partir du programme.
  • Est inefficace.
    • Il existe plusieurs conditions de terminaison de boucle, y compris la vérification de «vrai».
  • Est sujet aux bugs.
    • Impossible de déterminer facilement où placer le code qui s'exécutera toujours à chaque itération.
  • Conduit à un code inutilement complexe.
  • Analyse automatique du code source.
    • Pour trouver des bogues, analyser la complexité du programme, vérifier la sécurité ou dériver automatiquement tout autre comportement de code source sans exécution de code, la spécification des conditions de rupture initiales permet aux algorithmes de déterminer des invariants utiles, améliorant ainsi les métriques d'analyse automatique du code source.
  • Boucles infinies.
    • Si tout le monde utilise toujours while(true)des boucles qui ne sont pas infinies, nous perdons la capacité de communiquer de manière concise lorsque les boucles n'ont en fait aucune condition de terminaison. (On peut soutenir que cela s'est déjà produit, donc le point est sans objet.)

Alternative à "Aller à"

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. En plus le code:

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

Le deuxième point est important. Sans savoir comment fonctionne le code, si quelqu'un m'a demandé de faire en sorte que la boucle principale laisse aux autres threads (ou processus) un peu de temps CPU, deux solutions me viennent à l'esprit:

Option 1

Insérez facilement la pause:

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

Option 2

Remplacer l'exécution:

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

Ce code est plus simple (donc plus facile à lire) qu'une boucle avec un embarqué switch. La isValidStateméthode doit uniquement déterminer si la boucle doit continuer. La bête de somme de la méthode doit être abstraite dans la executeméthode, ce qui permet aux sous-classes de remplacer le comportement par défaut (une tâche difficile utilisant un switchet intégré goto).

Exemple Python

Comparez la réponse suivante (à une question Python) qui a été publié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 indéfiniment.
  4. Sinon, arrêtez de boucler pour toujours.
  5. Fin.
Code
while True: 
    choice = raw_input('What do you want? ')

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

print 'Break!' 

Contre:

  1. Initialisez le choix de l'utilisateur.
  2. Boucle tandis que le choix de l'utilisateur est le mot «redémarrer».
  3. Demandez à l'utilisateur de saisir son choix.
  4. Fin.
Code
choice = 'restart';

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

print 'Break!'

Ici, il en while Truerésulte un code trompeur et trop complexe.

Dave Jarvis
la source
45
L'idée qui while(true)devrait être nuisible me semble bizarre. Il y a des boucles qui vérifient avant de s'exécuter, il y a celles qui vérifient après et il y a celles qui vérifient au milieu. Puisque ces derniers n'ont pas de construction syntaxique en C et C ++, vous devrez utiliser while(true)ou for(;;). Si vous considérez que c'est faux, vous n'avez pas encore suffisamment réfléchi aux différents types de boucles.
sbi le
34
Raisons pour lesquelles je ne suis pas d'accord: while (true) et for (;;;) ne sont pas déroutants (contrairement à do {} while (true)) car ils indiquent ce qu'ils font tout de suite. Il est en fait plus facile de rechercher des instructions "break" que d'analyser une condition de boucle arbitraire et de chercher où elle est définie, et pas plus difficile de prouver l'exactitude. L'argument sur l'emplacement du code est tout aussi insoluble en présence d'une instruction continue, et pas beaucoup mieux avec les instructions if. L'argument de l'efficacité est complètement idiot.
David Thornley le
23
Chaque fois que vous voyez "while (true)", vous pouvez penser à une boucle qui a des conditions de terminaison internes, qui ne sont pas plus difficiles que des conditions de fin arbitraires. Simplement, une boucle "while (foo)", où foo est une variable booléenne définie de manière arbitraire, n'est pas meilleure que "while (true)" avec des sauts. Vous devez parcourir le code dans les deux cas. Avancer une raison idiote (et la micro-efficacité est un argument stupide) n'aide pas votre cas, d'ailleurs.
David Thornley le
5
@Dave: J'ai donné une raison: c'est le seul moyen d'obtenir une boucle qui vérifie la condition au milieu. Exemple classique: while(true) {string line; std::getline(is,line); if(!is) break; lines.push_back(line);}bien sûr, je pourrais transformer cela en une boucle préconditionnée (en utilisant std::getlinecomme condition de boucle), mais cela présente des inconvénients en soi ( linedevrait être une variable en dehors de la portée de la boucle). Et si je le faisais, je devrais demander: pourquoi avons-nous trois boucles de toute façon? Nous pourrions toujours tout transformer en une boucle préconditionnée. Utilisez ce qui vous convient le mieux. Si while(true)cela vous convient, utilisez-le.
sbi
3
Par courtoisie envers les gens qui lisent cette réponse, j'éviterais de commenter la qualité du bien qui a produit la question et m'en tenir à répondre à la question elle-même. La question est une variation de ce qui suit, qui, je crois, est une caractéristique de nombreux langages: vous avez une boucle imbriquée dans une autre boucle. Vous voulez sortir de la boucle externe de la boucle interne. Comment cela se fait-il?
Alex Leibowitz
172

Vous pouvez utiliser goto.

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

   }
}
exit_loop: ;
Mehrdad Afshari
la source
12
Ne vous découragez pas avec ce mot-clé.
Fragsworth le
45
C'est drôle que je reçois des votes négatifs parce que les gens n'aiment pas goto. Le PO est clairement mentionné sans utiliser de drapeaux . Pouvez-vous suggérer une meilleure façon, compte tenu des limites du PO? :)
Mehrdad Afshari
18
a voté pour compenser les haters de goto stupides. Je suppose que Mehrdad savait qu'il allait perdre quelques points pour avoir suggéré une solution sensée ici.
Michael Krelin - hacker le
6
+1, il n'y a pas de meilleur moyen à mon humble avis, même si l'on considère les limitations des OP. Les drapeaux sont laids, brisent la logique sur toute la boucle et donc plus difficiles à saisir.
Johannes Schaub - litb le
11
à la fin de la journée, sans la construction goto, tous les langages échouent. les langages de haut niveau l'obscurcissent, mais si vous regardez suffisamment sous le capot, vous trouverez que chaque boucle, ou condition se résume à des branches conditionnelles et inconditionnelles. nous nous trompons en utilisant des mots-clés for, while, until, switch, if, mais finalement ils utilisent tous GOTO (ou JMP) d'une manière ou d'une autre. vous pouvez vous leurrer en inventant toutes sortes de façons intelligentes de le cacher, ou vous pouvez simplement être honnête pragmatiquement et utiliser goto, le cas échéant, et avec parcimonie.
non synchronisé
58

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

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

Utilisez l' continueinstruction pour terminer chaque étiquette de cas où vous voulez que la boucle continue et utilisez l' breakinstruction pour terminer les étiquettes de cas qui doivent terminer la boucle.

Bien sûr, cette solution ne fonctionne que s'il n'y a pas de code supplémentaire à exécuter après l'instruction switch.

Sakra
la source
9
Bien que cela soit en effet très élégant, cela présente l'inconvénient que la plupart des développeurs doivent d'abord l'examiner pendant une minute afin de comprendre comment / si cela fonctionne. :(
sbi
8
La dernière phrase est ce qui rend ce pas si génial :(Sinon une implémentation très propre.
nawfal
Quand on y pense, aucun code informatique n'est bon. Nous sommes seulement formés pour le comprendre. Par exemple, xml ressemblait à une poubelle jusqu'à ce que je l'apprenne. Maintenant, c'est moins à la recherche de déchets. for (int x = 0; x <10; ++ x, moveCursor (x, y)) a en fait provoqué une erreur logique dans mon programme. J'ai oublié que la condition de mise à jour s'est produite à la fin.
22

Une manière élégante de faire ceci serait de mettre ceci dans une fonction:

int yourfunc() {

    while(true) {

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

    }
}

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

Alterlife
la source
4
L'exception doit être utilisée pour lancer une exception, pas un comportement bien connu d'une fonction.
Clement Herreman le
Je suis d'accord avec Afterlife: mettez-le dans une fonction.
dalle le
14
Lancer une exception est vraiment mal dans ce cas. Cela signifie "Je veux utiliser un goto, mais j'ai lu quelque part que je ne devrais pas les utiliser, alors je vais faire un subterfuge et faire semblant d'avoir l'air intelligent".
Gorpik le
Je conviens que lancer une exception ou utiliser un goto est une idée terrible, mais ce sont des options de travail et ont été répertoriées comme telles dans ma réponse.
Alterlife le
2
Lancer une exception consiste à remplacer un COMEFROM par un GOTO. Ne fais pas ça. Le goto fonctionne beaucoup mieux.
David Thornley le
14

AFAIK il n'y a pas de "double break" ou de construction similaire en C ++. Le plus proche serait un goto- qui, bien qu'il ait une mauvaise connotation pour son nom, existe dans la langue pour une raison - tant qu'il est utilisé avec prudence et parcimonie, c'est une option viable.

ambre
la source
8

Vous pouvez mettre votre commutateur dans une fonction distincte 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())
    ;
Adam Pierce
la source
7

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

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

J'utiliserais gotocomme dans cette réponse. Dans ce cas, gotole code sera plus clair que toute autre option. J'espère que cette question vous sera utile.

Mais je pense que l'utilisation gotoest la seule option ici à cause de la chaîne while(true). Vous devriez envisager de refactoriser votre boucle. Je suppose 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 ce qui suit:

while ( msg->state != DONE ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
}
Kirill V. Lyadvinsky
la source
1
Oui, mais je comme des exceptions encore moins ;-)
quamrana
3
Utiliser une exception pour émuler un goto est bien sûr pire que d'utiliser réellement un goto! Ce sera probablement aussi beaucoup plus lent.
John Carter
2
@Downvoter: C'est parce que je déclare, que vous ne devriez pas utiliser d'exception, ou parce que je suggère de refactoriser?
Kirill V.Lyadvinsky le
1
Pas l'électeur à la baisse - mais ... Votre réponse n'est pas claire si vous proposez ou condamnez l'idée d'utiliser une exception pour sortir de la boucle. Peut-être que la première phrase devrait être: "Même si vous n'aimez pas goto, n'utilisez pas d'exception pour quitter une boucle:"?
Jonathan Leffler le
1
@Jonathan Leffler, Merci, vous avez montré l'exemple de commentaire précieux. a mis à jour la réponse en gardant à l'esprit vos commentaires.
Kirill V.Lyadvinsky le
5

Il n'y a pas de construction C ++ pour sortir de la boucle dans ce cas.

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

dents acérées
la source
2

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

Martin York
la source
2

Pourquoi ne pas simplement corriger la condition dans votre boucle while, faisant disparaître le 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**
    }
}
Marque B
la source
2

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);
Prashanth Sriram
la source
1

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;
Ercan
la source
0

Le moyen le plus simple de le faire est de mettre un simple IF avant de faire le SWITCH, et ce IF teste votre condition pour sortir de la boucle .......... aussi simple que possible

SpiriAd
la source
2
Euh ... vous pourriez être encore plus simple en mettant cette condition dans l'argument while. Je veux dire que c'est pour ça qu'il est là! :)
Mark A. Donohoe
0

Le breakmot clé en C ++ termine uniquement l'itération ou l' switchinstruction englobante la plus imbriquée . Ainsi, vous ne pouviez pas sortir de la while (true)boucle directement dans l' switchinstruction; Cependant, vous pouvez utiliser le code suivant, 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 aviez besoin de faire quelque chose lorsque msg->stateégal à égal DONE(comme exécuter une routine de nettoyage), placez ce code immédiatement après la forboucle; 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();
}

Utilisez ensuite à la place:

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

    //...
    }
}

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

Je suis étonné de voir à quel point c'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!

Mark A. Donohoe
la source
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
la source
0

J'ai eu le même problème et je l'ai résolu à l'aide d'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;
}
Ram Suthar
la source
-2

Si je me souviens bien de la syntaxe C ++, vous pouvez ajouter une étiquette aux breakinstructions, comme pour goto. Donc, ce que vous voulez serait facilement écrit:

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
la source
1
Malheureusement, votre mémoire est défectueuse. Ce serait une fonctionnalité intéressante, dans les rares occasions où cela serait utile. (J'ai vu des propositions pour le nombre de niveaux à rompre, mais cela me semble comme des bugs en attente de se produire.)
David Thornley
Cela devait être Java, alors. Merci de répondre. Et, bien sûr, vous avez raison avec le reste, aussi.
Andrei Vajna II le
-3
  while(true)
  {
    switch(x)
    {
     case 1:
     {
      break;
     }
    break;
   case 2:
    //some code here
   break;
  default:
  //some code here
  }
}
Chevaughn
la source
1
Toutes les interruptions de cet exemple de code sortent de l'instruction switch.
Dan Hulme