174 votes

Avantage du switch par rapport à l'instruction if-else

Quelle est la meilleure pratique pour utiliser un switch par rapport à l'utilisation d'un if déclaration pour 30 unsigned où environ 10 ont une action attendue (qui est actuellement la même action). Les performances et l'espace doivent être pris en compte mais ne sont pas critiques. J'ai abstrait le snippet donc ne me détestez pas pour les conventions de nommage.

switch déclaration :

// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing

switch (numError)
{  
  case ERROR_01 :  // intentional fall-through
  case ERROR_07 :  // intentional fall-through
  case ERROR_0A :  // intentional fall-through
  case ERROR_10 :  // intentional fall-through
  case ERROR_15 :  // intentional fall-through
  case ERROR_16 :  // intentional fall-through
  case ERROR_20 :
  {
     fire_special_event();
  }
  break;

  default:
  {
    // error codes that require no additional action
  }
  break;       
}

if déclaration :

if ((ERROR_01 == numError)  ||
    (ERROR_07 == numError)  ||
    (ERROR_0A == numError)  || 
    (ERROR_10 == numError)  ||
    (ERROR_15 == numError)  ||
    (ERROR_16 == numError)  ||
    (ERROR_20 == numError))
{
  fire_special_event();
}

0 votes

Bien sûr, on peut le voir du point de vue de celui qui génère le code le plus efficace, mais tout compilateur moderne devrait être aussi efficace. En fin de compte, il s'agit plutôt d'une question de couleur de l'abri à vélo.

10 votes

Je ne suis pas d'accord, je ne pense pas que ce soit subjectif. Une simple différence ASM compte, vous ne pouvez pas simplement ignorer quelques secondes d'optimisation dans de nombreux cas. Et dans cette question, il ne s'agit pas d'une guerre ou d'un débat religieux, il y a une explication rationnelle de la raison pour laquelle l'un serait plus rapide, il suffit de lire la réponse acceptée.

2 votes

171voto

Nils Pipenbrinck Points 41006

Utilisez l'interrupteur.

Dans le pire des cas, le compilateur générera le même code qu'une chaîne if-else, vous ne perdez donc rien. En cas de doute, commencez par les cas les plus courants dans l'instruction switch.

Dans le meilleur des cas, l'optimiseur peut trouver une meilleure façon de générer le code. Les choses courantes qu'un compilateur fait sont de construire un arbre de décision binaire (économise les comparaisons et les sauts dans le cas moyen) ou de construire simplement une table de saut (fonctionne sans aucune comparaison).

3 votes

Techniquement, il y aura toujours une comparaison, pour s'assurer que la valeur de l'enum se trouve dans la table de saut.

0 votes

Jep. C'est vrai. En activant les enums et en traitant tous les cas, on peut se débarrasser de la dernière comparaison.

4 votes

Notez qu'une série de ifs pourrait théoriquement être analysée comme un switch par un compilateur, mais pourquoi prendre le risque ? En utilisant un switch, vous communiquez exactement ce que vous voulez, ce qui facilite la génération de code.

45voto

Mark Ransom Points 132545

Pour le cas particulier que vous avez fourni dans votre exemple, le code le plus clair est probablement le suivant :

if (RequiresSpecialEvent(numError))
    fire_special_event();

Évidemment, cela ne fait que déplacer le problème vers une autre zone du code, mais vous avez maintenant la possibilité de réutiliser ce test. Vous avez également plus d'options pour résoudre le problème. Vous pouvez utiliser std::set, par exemple :

bool RequiresSpecialEvent(int numError)
{
    return specialSet.find(numError) != specialSet.end();
}

Je ne dis pas que c'est la meilleure mise en œuvre de RequiresSpecialEvent, mais simplement que c'est une option. Vous pouvez toujours utiliser un switch ou une chaîne if-else, ou une table de consultation, ou une manipulation de bits sur la valeur, peu importe. Plus votre processus de décision est obscur, plus vous aurez intérêt à l'avoir dans une fonction isolée.

6 votes

C'est tellement vrai. La lisibilité est bien meilleure qu'avec le switch et les déclarations if. J'allais moi-même répondre à cette question, mais vous m'avez devancé :-)

1 votes

Si les valeurs de votre enum sont toutes petites, alors vous n'avez pas besoin d'un hash, juste d'une table, par exemple. const std::bitset<MAXERR> specialerror(initializer); Utilisez-le avec if (specialerror[numError]) { fire_special_event(); } . Si vous voulez un contrôle des limites, bitset::test(size_t) lèvera une exception sur les valeurs hors limites. ( bitset::operator[] ne vérifie pas la portée). cplusplus.com/reference/bitset/bitset/test . Cela sera probablement plus performant qu'une table de saut générée par le compilateur et mettant en œuvre switch dans le cas non particulier où il s'agira d'une seule branche non prise.

0 votes

@PeterCordes Je persiste à dire qu'il est préférable de placer le tableau dans sa propre fonction. Comme je l'ai dit, il y a lots d'options qui s'ouvrent lorsque vous faites cela, je n'ai pas essayé de les énumérer toutes.

24voto

paercebal Points 38526

L'interrupteur est plus rapide.

Essayez simplement de faire un if/else- de 30 valeurs différentes à l'intérieur d'une boucle, et comparez avec le même code utilisant un switch pour voir combien le switch est plus rapide.

Maintenant, le L'interrupteur a un vrai problème : Le commutateur doit connaître au moment de la compilation les valeurs à l'intérieur de chaque cas. Cela signifie que le code suivant :

// WON'T COMPILE
extern const int MY_VALUE ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

ne compilera pas.

La plupart des gens utiliseront alors des définitions (Aargh !), et d'autres déclareront et définiront des variables constantes dans la même unité de compilation. Par exemple :

// WILL COMPILE
const int MY_VALUE = 25 ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

En fin de compte, le développeur doit donc choisir entre "rapidité + clarté" et "couplage du code".

(Non pas qu'un interrupteur ne puisse pas être écrit pour être confus comme l'enfer... La plupart des switchs que je vois actuellement sont de cette catégorie "déroutante"... Mais c'est une autre histoire...)

Edit 2008-09-21 :

bk1e a ajouté le commentaire suivant : " La définition de constantes en tant qu'enums dans un fichier d'en-tête est une autre façon de gérer ce problème".

Bien sûr qu'elle l'est.

L'intérêt d'un type externe était de découpler la valeur de la source. Définir cette valeur en tant que macro, en tant que simple déclaration const int, ou même en tant qu'enum a pour effet secondaire d'inliner la valeur. Ainsi, si la définition, la valeur de l'enum ou la valeur de l'int constant changeait, une recompilation serait nécessaire. La déclaration extern signifie qu'il n'y a pas besoin de recompiler en cas de changement de valeur, mais d'un autre côté, elle rend impossible l'utilisation de switch. La conclusion étant L'utilisation de switch augmentera le couplage entre le code de switch et les variables utilisées comme cas. . Quand c'est bon, utilisez l'interrupteur. Quand ce n'est pas le cas, alors, pas de surprise.

.

Modifier 2013-01-15 :

Vlad Lazarenko a commenté ma réponse en donnant un lien vers son étude approfondie du code assembleur généré par un commutateur. Très instructif : http://lazarenko.me/switch/

0 votes

Définir des constantes en tant qu'enums dans un fichier d'en-tête est une autre façon de gérer cela.

6 votes

L'interrupteur est no toujours plus rapide .

1 votes

@Vlad Lazarenko : Merci pour le lien ! C'était une lecture très intéressante.

18voto

Richard Franks Points 1042

Le compilateur l'optimisera de toute façon - optez pour le switch car c'est le plus lisible.

3 votes

Il y a de fortes chances que le compilateur ne touche pas aux if-then-else. En effet, gcc ne le fera pas à coup sûr (il y a une bonne raison à cela). Clang optimisera les deux cas en une recherche binaire. Par exemple, voir este .

6voto

scubabbl Points 6776

Le commutateur, ne serait-ce que pour la lisibilité. Les "si" géants sont plus difficiles à maintenir et à lire, à mon avis.

ERROR_01 : // chute intentionnelle

ou

(ERROR_01 == numError) ||

Cette dernière est plus sujette aux erreurs et nécessite davantage de saisie et de formatage que la première.

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