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

101

Какой смысл в использовании {и }в 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;
}
Махмуд
источник
57
Одним из вариантов использования может быть ограничение объема переменных, объявленных в операторе case.
Abhishek Bansal
1
Слишком много отступов. Случаи - это просто метки в блоке оператора switch: они не вводят дополнительной вложенности, поэтому они должны быть согласованы с switchключевым словом, а во втором примере вложенные операторы имеют только один отступ. Обратите внимание, как у вас есть неудобное удаление отступа в четыре пробела после break;.
Kaz
Обратите внимание, что принятый ответ верен только частично, поскольку комментарий Джека указывает и пропускает некоторые тонкости, о которых я говорю в своем ответе.
Shafik Yaghmour
Точно так же, как к сведению: в C (даже C11), а не в C ++, вы не можете пометить объявление; они не входят в синтаксическую категорию statement. В C ++ можно (один из компонентов синтаксической категории statement- это declaration statement).
Джонатан Леффлер

Ответы:

195

{}Обозначает новый блок сферы .

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

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; 
    }
}
Rotem
источник
11
На самом деле IMO вы получите ошибку компилятора, даже если пропустите второе объявление переменной x.
Abhishek Bansal
1
Хотя чрезмерное использование этого стиля и размещение больших блоков внутри оператора switch сделает его нечитаемым для отслеживания случаев. Я предпочитаю, чтобы утверждения были крошечными.
masoud
2
@MatthieuM. Я точно знаю, что MS Visual Studio 2010 будет вести себя так, как указывает Абхишек: он не будет компилировать объявление какой-либо переменной внутри случая (если вы не используете фигурные скобки для обозначения новой области в этом случае). Я не знаю, соответствует ли это стандартам.
KRyan
1
@KRyan: нет, но это настолько безопасная альтернатива, что я вряд ли могу винить их за это.
Matthieu M.
6
Раздел 6.7 (3) стандарта (нумерация для черновика 2005 г.) указывает, что вы не можете перескакивать инициализацию, поэтому вы не можете иметь инициализацию в блоке case.
Джек Эйдли
23

TL; DR

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

Кровавые подробности

Мы можем видеть, что кейсы - это просто помеченные операторы, такие как метки, используемые с оператором goto ( это описано в разделе 6.1 проекта стандарта C ++ с пометкой ), и мы можем видеть из 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 ;
}

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

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

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

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

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
        }
}
Шафик Ягмур
источник
6

Это привычка, которая позволяет вам вставлять объявления переменных с результирующим деструктором (или конфликтами областей видимости) в 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";
}
пиджей
источник
1

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

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

красители
источник
0

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

  1. Удобочитаемость, он визуально увеличивает каждый случай как ограниченный раздел.
  2. Объявление разных переменных с одинаковым именем для нескольких случаев переключения.
  3. Микрооптимизации - область для очень дорогой переменной с выделенным ресурсом, которую вы хотите уничтожить, как только вы покинете область действия, или даже более спагетти-сценарий использования команды «GOTO».
Роман Амбиндер
источник