Usando {} en una declaración de caso. ¿Por qué?

101

¿Qué sentido tiene usar {y }en una casedeclaración? Normalmente, no importa cuántas líneas haya en una casedeclaración, todas las líneas se ejecutan. ¿Es esto solo una regla con respecto a los compiladores más antiguos / nuevos o hay algo detrás de eso?

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

y

int a = 0;
switch (a) {
  case 0:
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
}
Mahmood
fuente
57
Un uso puede ser limitar el alcance de las variables declaradas en la declaración del caso.
Abhishek Bansal
1
Demasiada sangría también. Los casos son solo etiquetas dentro del bloque de la instrucción de cambio: no introducen anidamiento adicional, por lo que deben alinearse con la switchpalabra clave y, en el segundo ejemplo, las declaraciones adjuntas solo se sangran una vez. Tenga en cuenta que tiene un incómodo desangrado de cuatro espacios después del break;.
Kaz
Tenga en cuenta que la respuesta aceptada es solo parcialmente correcta, como señala el comentario de Jack, y omite algunas sutilezas, que abordo en mi respuesta.
Shafik Yaghmour
Al igual que un FYI: en C (incluso C11) en lugar de C ++, no puede etiquetar una declaración; no están en la categoría sintáctica statement. En C ++, puede (un componente de la categoría sintáctica statementes declaration statement).
Jonathan Leffler

Respuestas:

195

El {}denota un nuevo bloque de alcance .

Considere el siguiente ejemplo muy elaborado:

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

Obtendrá un error del compilador porque xya está definido en el alcance.

Separarlos en su propio subámbito eliminará la necesidad de declarar xfuera de la declaración de cambio.

switch (a)
{
    case 42: {
        int x = GetSomeValue();
        return a * x; 
    }
    case 1337: {
        int x = GetSomeOtherValue(); //OK
        return a * x; 
    }
}
Rotem
fuente
11
En realidad, en mi opinión, obtendrá un error del compilador incluso si omite la segunda declaración de la variable x.
Abhishek Bansal
1
Aunque el uso excesivo de este estilo y la colocación de grandes bloques dentro de la declaración de cambio hará que sea ilegible seguir los casos. Prefiero que las declaraciones sean pequeñas.
masoud
2
@MatthieuM. Sé a ciencia cierta que MS Visual Studio 2010 tendrá el comportamiento que indica Abhishek: no compilará ninguna declaración de variable dentro de un caso (a menos que use llaves para indicar un nuevo alcance dentro de ese caso). Si eso coincide con los estándares, no lo sé.
KRyan
1
@KRyan: no es así, pero es una alternativa mucho más segura que difícilmente puedo culparlos por hacer cumplir esto.
Matthieu M.
6
La sección 6.7 (3) del estándar (numeración para el borrador de 2005) especifica que no puede saltar una inicialización, por lo que no puede tener inicialización en un bloque de casos.
Jack Aidley
23

TL; DR

La única forma en que puede declarar una variable con un inicializador o algún objeto no trivial dentro de un caso es introducir un alcance de bloque usando {}u otra estructura de control que tenga su propio alcance como un bucle o una declaración if .

Detalles sangrientos

Podemos ver que los casos son solo declaraciones etiquetadas como las etiquetas que se usan con una declaración goto ( esto se cubre en la sección 6.1 Declaración etiquetada del borrador del estándar de C ++ ) y podemos ver en el 6.7párrafo 3 de la sección que saltar una declaración no está permitido en muchos casos , incluidos aquellos con una inicialización:

Es posible transferir a un bloque, pero no de una manera que omita las declaraciones con la inicialización. Un programa que salta 87 desde un punto donde una variable con duración de almacenamiento automático no está dentro del alcance a un punto donde está dentro del alcance está mal formado a menos que la variable tenga un tipo escalar, un tipo de clase con un constructor predeterminado trivial y un destructor trivial, una versión calificada por cv de uno de estos tipos, o una matriz de uno de los tipos anteriores y se declara sin un inicializador (8.5).

y proporciona este ejemplo:

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
}

Tenga en cuenta que aquí hay algunas sutilezas, se le permite saltar más allá de una declaración escalar que no tiene una inicialización, por ejemplo:

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

es perfectamente válido ( ejemplo vivo ). Por supuesto, si desea declarar la misma variable en cada caso , cada uno necesitará su propio alcance, pero también funciona de la misma manera fuera de las declaraciones de cambio , por lo que no debería ser una gran sorpresa.

En cuanto a la justificación para no permitir el salto después de la inicialización, el informe de defectos 467, aunque cubre un problema ligeramente diferente, proporciona un caso razonable para las variables automáticas :

[...] las variables automáticas, si no se inicializan explícitamente, pueden tener valores indeterminados ("basura"), incluidas las representaciones de trampas, [...]

Probablemente sea más interesante observar el caso en el que extiende un alcance dentro de un interruptor sobre varios casos, los ejemplos más famosos de esto probablemente sean el dispositivo de Duff, que se vería así:

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
fuente
6

Es un hábito que le permite inyectar declaraciones de variables con el destructor resultante (o conflictos de alcance) en casecláusulas. Otra forma de verlo es que están escribiendo para el lenguaje que desearían tener, donde todo el control de flujo consiste en bloques y no en secuencias de declaraciones.

Yakk - Adam Nevraumont
fuente
4

Verifique que esta es una restricción básica del compilador y comenzará a preguntarse qué está pasando:

int c;
c=1;

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

    default : cout<<"def";
}

Esto le dará un error:

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

Si bien este no lo hará:

int c;
c=1;

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

    default : cout<<"def";
}
pj
fuente
1

El uso de corchetes en el interruptor denota un nuevo bloque de alcance como lo dijo Rotem.

Pero también puede serlo para mayor claridad al leer. Para saber dónde se detiene el caso, ya que podría tener una ruptura condicional.

tintes
fuente
0

Las razones pueden ser:

  1. Legibilidad, mejora visualmente cada caso como una sección con alcance.
  2. Declarar las diferentes variables con el mismo nombre para varios casos de interruptores.
  3. Micro optimizaciones: alcance para una variable de recursos asignados realmente costosa que desea destruir tan pronto como deje el alcance del caso, o incluso un escenario más espagueti del uso del comando "GOTO".
Ambinder romano
fuente