Usando {} em uma declaração de caso. Por quê?

101

Qual é o objetivo de usar {e }em uma casedeclaração? Normalmente, não importa quantas linhas existem em uma caseinstrução, todas as linhas são executadas. Esta é apenas uma regra sobre compiladores mais antigos / mais recentes ou há algo por trás disso?

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

e

int a = 0;
switch (a) {
  case 0:
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
}
Mahmood
fonte
57
Um uso pode ser limitar o escopo das variáveis ​​declaradas na instrução case.
Abhishek Bansal
1
Muito recuo também. Os casos são apenas rótulos dentro do bloco da instrução switch: eles não introduzem aninhamento adicional, portanto, eles devem se alinhar com a switchpalavra - chave e, no segundo exemplo, as instruções incluídas recuaram apenas uma vez. Observe como você tem um desajuste de quatro espaços de recuo após o break;.
Kaz
Observe, a resposta aceita é apenas parcialmente correta, pois o comentário de Jack aponta e omite algumas sutilezas, que abordo em minha resposta.
Shafik Yaghmour
Assim como um FYI: em C (mesmo C11) em vez de C ++, você não pode rotular uma declaração; eles não estão na categoria sintática statement. Em C ++, você pode (um componente da categoria sintática statementé declaration statement).
Jonathan Leffler

Respostas:

195

O {}denota um novo bloco de escopo .

Considere o seguinte exemplo muito artificial:

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

Você obterá um erro do compilador porque xjá está definido no escopo.

Separá-los em seu próprio sub-escopo eliminará a necessidade de declarar xfora da instrução switch.

switch (a)
{
    case 42: {
        int x = GetSomeValue();
        return a * x; 
    }
    case 1337: {
        int x = GetSomeOtherValue(); //OK
        return a * x; 
    }
}
Rotem
fonte
11
Na verdade, IMO, você obterá um erro do compilador mesmo se pular a segunda declaração da variável x.
Abhishek Bansal
1
Embora usar esse estilo e colocar blocos grandes dentro da instrução switch tornará ilegível seguir os casos. Prefiro manter as declarações minúsculas.
Masoud de
2
@MatthieuM. Eu sei com certeza que o MS Visual Studio 2010 terá o comportamento que Abhishek indica: ele não irá compilar nenhuma declaração de variável dentro de um caso (a menos que você use chaves para denotar um novo escopo nesse caso). Se isso corresponde aos padrões, não sei.
KRyan de
1
@KRyan: não, mas é uma alternativa muito mais segura que dificilmente posso culpá-los por impor isso.
Matthieu M.
6
A seção 6.7 (3) da norma (numeração para o rascunho de 2005) especifica que você não pode pular uma inicialização, então você não pode ter inicialização em um bloco de caso.
Jack Aidley
23

TL; DR

A única maneira de declarar uma variável com um inicializador ou algum objeto não trivial dentro de um caso é introduzir um escopo de bloco usando {}ou outra estrutura de controle que tenha seu próprio escopo como um loop ou instrução if .

Detalhes sangrentos

Podemos ver que os casos são apenas instruções rotuladas como os rótulos usados ​​com uma instrução goto ( isso é abordado no esboço do padrão C ++ seção 6.1 Instrução rotulada ) e podemos ver na seção 6.7parágrafo 3 que pular uma declaração não é permitido em muitos casos , incluindo aqueles com uma inicialização:

É possível transferir para um bloco, mas não de uma forma que ignore as declarações com a inicialização. Um programa que salta 87 de um ponto onde uma variável com duração de armazenamento automático não está no escopo para um ponto onde está no escopo é malformado, a menos que a variável tenha tipo escalar, tipo de classe com um construtor padrão trivial e um destruidor trivial, uma versão cv qualificada de um desses tipos, ou uma matriz de um dos tipos anteriores e é declarada sem um inicializador (8.5).

e fornece este exemplo:

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
}

Observe que existem algumas sutilezas aqui, você pode pular uma declaração escalar que não possui uma inicialização, por exemplo:

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

é perfeitamente válido ( exemplo ao vivo ). Obviamente, se você quiser declarar a mesma variável em cada caso , cada um deles precisará de seu próprio escopo, mas funciona da mesma maneira fora das instruções switch , de modo que não deve ser uma grande surpresa.

Quanto à justificativa para não permitir o salto além da inicialização, o relatório de defeito 467, embora abranja uma questão ligeiramente diferente, fornece um caso razoável para variáveis ​​automáticas :

[...] variáveis ​​automáticas, se não inicializadas explicitamente, podem ter valores indeterminados ("lixo"), incluindo representações de trap, [...]

É provavelmente mais interessante olhar para o caso em que você estende um escopo dentro de um switch sobre vários casos. Os exemplos mais famosos disso são provavelmente o dispositivo de Duff, que seria mais ou menos assim:

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

É um hábito que permite injetar declarações de variáveis ​​com o destruidor resultante (ou conflitos de escopo) nas casecláusulas. Outra maneira de ver isso é que eles estão escrevendo para a linguagem que gostariam de ter, onde todo o controle de fluxo consiste em blocos e não sequências de declarações.

Yakk - Adam Nevraumont
fonte
4

Verifique esta uma restrição básica do compilador e você começará a se perguntar o que está acontecendo:

int c;
c=1;

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

    default : cout<<"def";
}

Isso gerará um erro:

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

Embora este não:

int c;
c=1;

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

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

O uso de colchetes no switch denota um novo bloco de escopo, conforme dito por Rotem.

Mas também pode ser para maior clareza ao ler. Para saber onde o caso termina, pois você pode ter uma quebra condicional nele.

tinturas
fonte
0

Os motivos podem ser:

  1. Legibilidade, aprimora visualmente cada caso como uma seção com escopo definido.
  2. Declarando as variáveis ​​a diferentes com o mesmo nome para vários casos de switch.
  3. Micro otimizações - escopo para uma variável alocada de recurso realmente cara que você deseja destruir assim que sair do escopo do caso, ou até mesmo um cenário mais espaguete de uso do comando "GOTO".
Roman Ambinder
fonte