Utilisation de {} dans une instruction case. Pourquoi?

101

Quel est l'intérêt d'utiliser {et }dans une casedéclaration? Normalement, quel que soit le nombre de lignes présentes dans une caseinstruction, toutes les lignes sont exécutées. Est-ce juste une règle concernant les compilateurs plus anciens / plus récents ou il y a quelque chose derrière cela?

int a = 0;
switch (a) {
  case 0:{
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
  }
}

et

int a = 0;
switch (a) {
  case 0:
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
}
Mahmood
la source
57
Une utilisation peut être de limiter la portée des variables déclarées dans l'instruction case.
Abhishek Bansal
1
Trop d'indentation aussi. Les cas ne sont que des étiquettes dans le bloc de l'instruction switch: ils n'introduisent pas d'imbrication supplémentaire, ils doivent donc s'aligner sur le switchmot - clé, et dans le deuxième exemple, les instructions incluses ne sont indentées qu'une seule fois. Notez que vous avez un désindentation maladroite de quatre espaces après le break;.
Kaz
Notez que la réponse acceptée n'est que partiellement correcte, comme le souligne le commentaire de Jack et manque certaines subtilités, que j'aborde dans ma réponse.
Shafik Yaghmour
Tout comme un FYI: en C (même C11) plutôt qu'en C ++, vous ne pouvez pas étiqueter une déclaration; ils ne sont pas dans la catégorie syntaxique statement. En C ++, vous pouvez (un composant de la catégorie syntaxique statementest declaration statement).
Jonathan Leffler

Réponses:

195

Le {}désigne un nouveau bloc de portée .

Prenons l'exemple très artificiel suivant:

switch (a)
{
    case 42:
        int x = GetSomeValue();
        return a * x;
    case 1337:
        int x = GetSomeOtherValue(); //ERROR
        return a * x;
}

Vous obtiendrez une erreur du compilateur car elle xest déjà définie dans la portée.

Séparer ces derniers en leur propre sous-étendue éliminera le besoin de déclarer en xdehors de l'instruction switch.

switch (a)
{
    case 42: {
        int x = GetSomeValue();
        return a * x; 
    }
    case 1337: {
        int x = GetSomeOtherValue(); //OK
        return a * x; 
    }
}
Rotem
la source
11
En fait, IMO, vous obtiendrez une erreur de compilation même si vous ignorez la deuxième déclaration de la variable x.
Abhishek Bansal
1
Bien que le fait d'utiliser ce style et de mettre de gros blocs dans l'instruction switch rendra illisible le suivi des cas. Je préfère garder les déclarations minuscules.
masoud du
2
@MatthieuM. Je sais pertinemment que MS Visual Studio 2010 aura le comportement indiqué par Abhishek: il ne compilera aucune déclaration de variable à l'intérieur d'un cas (à moins que vous n'utilisiez des accolades pour désigner une nouvelle portée dans ce cas). Si cela correspond aux normes, je ne sais pas.
KRyan
1
@KRyan: ce n'est pas le cas, mais c'est une alternative tellement plus sûre que je peux difficilement les blâmer de faire respecter cela.
Matthieu M.
6
La section 6.7 (3) de la norme (numérotation pour le projet de 2005) spécifie que vous ne pouvez pas sauter une initialisation, donc vous ne pouvez pas avoir d'initialisation dans un bloc de cas.
Jack Aidley
23

TL; DR

La seule façon de déclarer une variable avec un initiateur ou un objet non trivial dans un cas est d'introduire une portée de bloc en utilisant {}ou une autre structure de contrôle qui a sa propre portée comme une boucle ou une instruction if .

Détails sanglants

Nous pouvons voir que les cas ne sont que des instructions étiquetées comme les étiquettes utilisées avec une instruction goto ( cela est couvert dans le projet de norme C ++ section 6.1 Instruction étiquetée ) et nous pouvons voir dans la section 6.7paragraphe 3 que sauter une déclaration n'est pas autorisé dans de nombreux cas , y compris ceux avec une initialisation:

Il est possible de transférer dans un bloc, mais pas d'une manière qui contourne les déclarations avec l'initialisation. Un programme qui saute 87 d'un point où une variable avec une durée de stockage automatique n'est pas dans la portée à un point où elle est dans la portée est mal formé sauf si la variable a un type scalaire, un type de classe avec un constructeur par défaut trivial et un destructeur trivial, une version qualifiée cv de l'un de ces types, ou un tableau de l'un des types précédents et est déclaré sans initialiseur (8.5).

et donne cet exemple:

void f() {
 // ...
 goto lx; // ill-formed: jump into scope of a

 ly:
  X a = 1;
 // ...
 lx:
  goto ly; // OK, jump implies destructor
          // call for a followed by construction
          // again immediately following label ly
}

Notez qu'il y a quelques subtilités ici, vous êtes autorisé à sauter une déclaration scalaire qui n'a pas d'initialisation, par exemple:

switch( n ) 
{
    int x ;
    //int x  = 10 ; 
    case 0:
      x = 0 ;
      break;
    case 1:
      x = 1 ;
      break;
    default:
      x = 100 ;
      break ;
}

est parfaitement valide ( exemple en direct ). Bien sûr, si vous voulez déclarer la même variable dans chaque cas, ils auront chacun besoin de leur propre portée, mais cela fonctionne de la même manière en dehors des instructions switch , donc cela ne devrait pas être une grande surprise.

En ce qui concerne la justification de ne pas autoriser le saut d'initialisation, le rapport de défaut 467, bien que couvrant un problème légèrement différent, fournit un cas raisonnable pour les variables automatiques :

[...] les variables automatiques, si elles ne sont pas explicitement initialisées, peuvent avoir des valeurs indéterminées («garbage»), y compris des représentations d'interruption, [...]

Il est probablement plus intéressant de regarder le cas où vous étendez une portée dans un basculement sur plusieurs cas, les exemples les plus célèbres de ceci sont probablement l'appareil de Duff qui ressemblerait à ceci:

void send( int *to, const int *from, int  count)
{
        int n = (count + 7) / 8;
        switch(count % 8) 
        {
            case 0: do {    *to = *from++;   // <- Scope start
            case 7:         *to = *from++;
            case 6:         *to = *from++;
            case 5:         *to = *from++;
            case 4:         *to = *from++;
            case 3:         *to = *from++;
            case 2:         *to = *from++;
            case 1:         *to = *from++;
                        } while(--n > 0);    // <- Scope end
        }
}
Shafik Yaghmour
la source
6

C'est une habitude qui vous permet d'injecter des déclarations de variables avec le destructeur résultant (ou les conflits de portée) dans les caseclauses. Une autre façon de voir les choses est qu'ils écrivent pour le langage qu'ils aimeraient avoir, où tout contrôle de flux se compose de blocs et non de séquences d'instructions.

Yakk - Adam Nevraumont
la source
4

Vérifiez ceci une restriction de base du compilateur et vous commencerez à vous demander ce qui se passe:

int c;
c=1;

switch(c)
{
    case 1:
    //{
        int *i = new int;
        *i =1;
        cout<<*i;
        break;
    //}

    default : cout<<"def";
}

Cela vous donnera une erreur:

error: jump to case label [-fpermissive]
error:   crosses initialization of int* i

Bien que celui-ci ne:

int c;
c=1;

switch(c)
{
    case 1:
    {
        int *i = new int;
        *i =1;
        cout<<*i;
        break;
    }

    default : cout<<"def";
}
p j
la source
1

L'utilisation de crochets dans le commutateur indique un nouveau bloc de portée comme l'a dit Rotem.

Mais cela peut l'être aussi pour plus de clarté lorsque vous lisez. Pour savoir où le cas s'arrête car vous pourriez y avoir une rupture conditionnelle.

colorants
la source
0

Les raisons peuvent être:

  1. Lisibilité, il améliore visuellement chaque cas en tant que section de portée.
  2. Déclarer les différentes variables avec le même nom pour plusieurs cas de commutation.
  3. Micro optimisations - possibilité d'une variable allouée aux ressources très coûteuse que vous souhaitez détruire dès que vous quittez le champ d'application du cas, ou même un scénario plus spaghetti de l'utilisation de la commande "GOTO".
Ambinder romain
la source