Использование {} в инструкции case. Зачем?

В чем смысл использования { и } в инструкции case? Обычно, независимо от того, сколько строк есть в инструкции case, все строки выполняются. Это просто правило относительно старших/более новых компиляторов или что-то позади?

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

и

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

Ответ 1

{} обозначает новый блок scope.

Рассмотрим следующий очень надуманный пример:

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

Вы получите ошибку компилятора, поскольку x уже определен в области.

Разделение их на их собственную под-область исключает необходимость объявлять x вне оператора switch.

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

Ответ 2

TL; DR

Единственный способ объявить переменную с помощью intializer или некоторого нетривиального объекта внутри случая - ввести область блока с помощью {} или другой структуры управления, которая имеет свою собственную область действия как цикл или оператор if.

Детали Gory

Мы видим, что случаи - это просто помеченные выражения, такие как метки, используемые с оператором goto (это описано в Светодиодный проект стандарта, раздел 6.1 Обозначенный оператор), и мы можем видеть из раздела 6.7, пункт 3, что во многих случаях пропускать объявление не разрешается, в том числе с инициализацией

Можно передать в блок, но не таким образом, чтобы обходить объявления с инициализацией. Программа, которая перескакивает 87 из точки, где переменная с длительностью автоматического хранения не находится в области до точки, где она находится в области, плохо сформирована, если переменная не имеет скалярного типа, тип класса с тривиальным конструктор по умолчанию и тривиальный деструктор, cv-квалифицированная версия одного из этих типов или массив одного из предыдущих типов и объявляется без инициализатора (8.5).

и предоставляет этот пример:

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
}

Обратите внимание, что здесь есть несколько тонкостей, вам разрешено перескакивать через скалярное объявление, которое не имеет инициализации, например:

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

отлично (живой пример). Конечно, если вы хотите объявить одну и ту же переменную в каждом случае, то каждый из них будет нуждаться в собственной области, но он работает так же, как и вне операторов switch, поэтому это не должно быть большим сюрпризом.

Что касается обоснования отказа от предыдущей инициализации перехода, отчет о дефекте 467, хотя при рассмотрении немного другой проблемы предоставляется разумный аргумент для автоматических переменных:

[...] автоматические переменные, если они явно не инициализированы, могут иметь неопределенные значения ( "мусор" ), включая ловушки, [...]

Вероятно, более интересно взглянуть на случай, когда вы расширяете сферу действия в пределах коммутатора по нескольким случаям, наиболее известными примерами этого является, вероятно, устройство Duff, которое выглядите примерно так:

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
        }
}

Ответ 3

Это привычка, которая позволяет вам вводить объявления переменных с помощью результирующего деструктора (или конфликтов областей) в предложения case. Другой способ взглянуть на это - это писать для языка, который они хотели бы, когда все управление потоком состоит из блоков, а не последовательностей операторов.

Ответ 4

Проверьте это основное ограничение компилятора, и вы начнете задаваться вопросом, что происходит:

int c;
c=1;

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

    default : cout<<"def";
}

Это даст вам сообщение об ошибке:

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

Пока этого не будет:

int c;
c=1;

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

    default : cout<<"def";
}

Ответ 5

Использование скобок в коммутаторе означает новый блок области действия, указанный Rotem.

Но это может быть и для ясности, когда вы читаете. Чтобы узнать, где останавливается случай, поскольку у вас может быть условный разрыв в нем.

Ответ 6

Причины могут быть:

  • Считываемость, визуально улучшает каждый случай как раздел с областью.
  • Объявление различных переменных с одинаковым именем для нескольких случаев переключения.
  • Микрооптимизация - область для действительно дорогостоящей ресурсной выделенной переменной, которую вы хотите уничтожить, как только вы покинете область действия, или даже более сценарий спагетти использования команды "GOTO".