235 votes

Quelles sont les meilleures façons d'éviter le do-while (0); pirater en C ++?

Lorsque le flux de code est comme ceci:

 if(check())
{
  ...
  ...
  if(check())
  {
    ...
    ...
    if(check())
    {
      ...
      ...
    }
  }
}
 

J'ai généralement vu ce travail autour pour éviter le flux de code ci-dessus désordonné:

 do {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);
 

Quelles sont les meilleures façons d'éviter ce contournement / piratage pour qu'il devienne un code de niveau supérieur (au niveau de l'industrie)?

Toutes les suggestions qui sont hors de la boîte sont les bienvenues!

312voto

Mikhail Points 6770

Il est considéré comme une pratique acceptable d'isoler ces décisions dans une fonction et d'utiliser return s au lieu de break s. Alors que tous ces contrôles correspondent au même niveau d'abstraction que de la fonction, c'est une approche assez logique.

Par exemple:

 void func(output_parameters, input_parameters)
{
   if (!condition)
   {
      return;
   }
   ... 
   ...
   if (other condition)
   {
      return;
   }
   ...
   if (!another condition)
   {
      return;
   }
   ... 
   if (!yet another condition)
   {
      return;
   }
   ... 
   .... 
   if (...)
         ...    // No need to use return here. 

}

void func_and_finish(){
   setup of lots of stuff
   ...
   func(stuff_needed_in_finish, stuff_needed_in_func)
   finish up
}
 

262voto

Mats Petersson Points 70074

Il y a des moments lors de l'utilisation d' goto est en fait le DROIT de réponse - au moins pour ceux qui ne sont pas éduqués dans la foi religieuse,"goto peut-être jamais la réponse, peu importe ce que la question est" - et c'est l'un de ces cas.

Ce code est d'utiliser le hack de do { ... } while(0); pour le seul but de s'habiller un goto comme break. Si vous allez utiliser goto, puis être ouvert à ce sujet. Il n'est point de rendre le code plus DIFFICILE à lire.

Une situation particulière est seulement quand vous avez beaucoup de code assez complexe de conditions:

void func()
{
   setup of lots of stuff
   ...
   if (condition)
   {
      ... 
      ...
      if (!other condition)
      {
          ...
          if (another condition)
          {
              ... 
              if (yet another condition)
              {
                  ...
                  if (...)
                     ... 
              }
          }
      }
  .... 

  }
  finish up. 
}

Il peut le faire plus clair que le code est correct de ne pas avoir une telle logique complexe.

void func()
{
   setup of lots of stuff
   ...
   if (!condition)
   {
      goto finish;
   }
   ... 
   ...
   if (other condition)
   {
      goto finish;
   }
   ...
   if (!another condition)
   {
      goto finish;
   }
   ... 
   if (!yet another condition)
   {
      goto finish;
   }
   ... 
   .... 
   if (...)
         ...    // No need to use goto here. 
 finish:
   finish up. 
}

Edit: Pour préciser, je ne suis en aucun cas suggérant l'utilisation d' goto comme une solution générale. Mais il y a des cas où l' goto est une solution meilleure que les autres solutions.

Imaginez par exemple que nous sommes de la collecte de certaines données, et les différentes conditions testées sont une sorte de "c'est la fin des données recueillies" - qui dépend d'une sorte de "continuer" marqueurs qui varient selon l'endroit où vous êtes dans le flux de données.

Maintenant, quand nous aurons terminé, nous avons besoin d'enregistrer les données dans un fichier.

Et oui, il y a souvent d'autres solutions que peut offrir une solution raisonnable, mais pas toujours.

82voto

dasblinkenlight Points 264350

Vous pouvez utiliser un simple modèle de continuation avec une bool variable:

bool goOn;
if ((goOn = check0())) {
    ...
}
if (goOn && (goOn = check1())) {
    ...
}
if (goOn && (goOn = check2())) {
    ...
}
if (goOn && (goOn = check3())) {
    ...
}

Cette chaîne de l'exécution s'arrête dès qu' checkN renvoie un false. Aucune autre check...() des appels serait effectué en raison d'un court-circuit de l' && de l'opérateur. En outre, l'optimisation des compilateurs sont assez intelligent pour reconnaître que le paramètre goOn de false est une rue à sens unique, et insérez le manque goto end pour vous. Comme le résultat, la performance du code ci-dessus doit être identique à celle d'un do/while(0), seul, sans un coup douloureux à sa lisibilité.

38voto

Mike Seymour Points 130519
  1. Essayez d'extraire le code dans une fonction séparée (ou peut-être plus d'un). Alors de retour de la fonction, si la vérification échoue.

  2. Si c'est trop serré avec les environs de code pour le faire, et vous ne pouvez pas trouver un moyen de réduire le couplage, regardez le code après ce bloc. Sans doute, il nettoie des ressources utilisées par la fonction. Essayez de gérer ces ressources à l'aide d'un RAII objet; ensuite, remplacer tous pourris break avec return (ou throw, si ce n'est plus le cas) et laissez-le destructeur de l'objet à nettoyer pour vous.

  3. Si le déroulement du programme est (forcément) donc ondulés que vous avez vraiment besoin d'un goto, puis l'utiliser plutôt que de les donner un déguisement bizarre.

  4. Si vous avez des règles de codage que l'aveuglette interdire goto, et vous avez vraiment ne peut pas simplifier le déroulement du programme, alors vous aurez probablement à la camoufler avec votre do hack.

37voto

utnapistim Points 12060

TLDR: RAII, transactionnelle code (uniquement les résultats ou le retour des choses quand elle est déjà calculée) et les exceptions.

Réponse longue:

En C, la meilleure pratique pour ce genre de code est à ajouter une SORTIE/NETTOYAGE/d'autres d'étiquette dans le code, où le nettoyage de ressources locales qui se passe et un code d'erreur (le cas échéant) est retourné. C'est une pratique exemplaire car il divise code naturellement dans l'initialisation, de calcul, de s'engager et de retour:

error_code_type c_to_refactor(result_type *r)
{
    error_code_type result = error_ok; //error_code_type/error_ok defd. elsewhere
    some_resource r1, r2; // , ...;
    if(error_ok != (result = computation1(&r1))) // Allocates local resources
        goto cleanup;
    if(error_ok != (result = computation2(&r2))) // Allocates local resources
        goto cleanup;
    // ...

    // Commit code: all operations succeeded
    *r = computed_value_n;
cleanup:
    free_resource1(r1);
    free_resource2(r2);
    return result;
}

En C, dans la plupart des bases de codes, if(error_ok != ... et goto code est habituellement caché derrière quelques commodité des macros (RET(computation_result), ENSURE_SUCCESS(computation_result, return_code), etc.).

C++ offre des outils supplémentaires en plus de C:

  • Le nettoyage de la fonctionnalité de blocage peut être mis en œuvre RAII, ce qui signifie que vous n'avez plus besoin de l'ensemble de l' cleanup bloc et l'activation du code client pour ajouter un début de retour des déclarations.

  • Vous jetez chaque fois que vous ne pouvez pas continuer, transformant toutes les if(error_ok != ... en direct des appels.

L'équivalent de code C++:

result_type cpp_code()
{
    raii_resource1 r1 = computation1();
    raii_resource2 r2 = computation2();
    // ...
    return computed_value_n;
}

C'est la meilleure pratique parce que:

  • Il est explicite (qui est, tandis que la gestion d'erreur n'est pas explicite, le flux principal de l'algorithme est)

  • Il est simple d'écrire du code client

  • Il est minime

  • C'est simple

  • Il n'a pas de code répétitif des constructions

  • Il n'utilise pas les macros

  • Il n'utilise pas étrange do { ... } while(0) des constructions

  • Il est réutilisable avec un minimum d'effort (qui est, si je veux copier l'appel à computation2(); pour une fonction différente, je n'ai pas de assurez-je ajouter un do { ... } while(0) dans le nouveau code, ni #define goto wrapper macro et un nettoyage de l'étiquette, ni rien d'autre).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X