在 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;
}
14275 次浏览

The {} denotes a new block of scope.

Consider the following very contrived example:

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

You will get a compiler error because x is already defined in the scope.

Separating these to their own sub-scope will eliminate the need to declare x outside the switch statement.

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

Warning:

Declare and initialize a variable inside case without {} surrounded is wrong:

#include <iostream>
using namespace std;
int main() {
int b = 3;
switch (b) {
case 3:
int a = 3; //compilation error: "initialization of 'a' skipped by 'case' label"
return a * b;
case 1:
return a * b;
}
}

It is a habit that allows you to inject variable declarations with the resulting destructor (or scope conflicts) into case clauses. Another way of looking at it is they are writing for the language they wish they had, where all flow control consists of blocks and not sequences of statements.

Check this a basic compiler restriction and you will start wondering whats happening :

int c;
c=1;


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


default : cout<<"def";
}

This will give you an error:

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

While this one will not:

int c;
c=1;


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


default : cout<<"def";
}

Using brackets in switch denotes a new block of scope as said by Rotem.

But it can be as well for clarity when you read. To know where the case stops as you might have conditionnal break in it.

TL;DR

The only way you can declare a variable with an intializer or some non-trivial object inside a case is to introduce a block scope using {} or other control structure that has it's own scope like a loop or if statement.

Gory details

We can see that cases are just labeled statements like the labels used with a goto statement(this is covered in the C++ draft standard section 6.1 Labeled statement) and we can see from section 6.7 paragraph 3 that jumping pass a declaration is not allowed in many cases, including those with an initialization:

It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps87 from a point where a variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has scalar type, class type with a trivial default constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the preceding types and is declared without an initializer (8.5).

and provides this example:

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
}

Note, there are some subtleties here, you are allowed to jump past a scalar declaration that does not have an initialization, for example:

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

is perfectly valid(live example). Of course if you want to declare the same variable in each case then they will each need their own scope but it works the same way outside of switch statements as well, so that should not be a big surprise.

As for the rationale for not allowing jump past initialization, defect report 467 although covering a slightly different issue provides a reasonable case for automatic variables:

[...]automatic variables, if not explicitly initialized, can have indeterminate (“garbage”) values, including trap representations, [...]

It is probably more interesting to look at the case where you extend a scope within a switch over multiple cases the most famous examples of this is probably Duff's device which would look something like this:

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

The reasons might be:

  1. Readability, it visually enhances each case as a scoped section.
  2. Declaring the a different variables with the same name for several switch cases.
  3. Micro optimizations- scope for a really expensive resource allocated variable that you want to destruct as soon as you leave the scope of the case, or even a more spaghetti scenario of the "GOTO" command usage.