Verwenden von {} in einer case-Anweisung. Warum?

101

Was ist der Sinn bei der Verwendung {und }in einer caseAnweisung? Normalerweise werden casealle Zeilen ausgeführt , unabhängig davon, wie viele Zeilen in einer Anweisung enthalten sind. Ist dies nur eine Regel für ältere / neuere Compiler oder steckt etwas dahinter?

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

und

int a = 0;
switch (a) {
  case 0:
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
}
Mahmood
quelle
57
Eine Verwendung kann darin bestehen, den Umfang der in der case-Anweisung deklarierten Variablen zu begrenzen.
Abhishek Bansal
1
Auch zu viel Einrückung. Die Fälle sind nur Beschriftungen innerhalb des Blocks der switch-Anweisung: Sie führen keine zusätzliche Verschachtelung ein, daher sollten sie mit dem switchSchlüsselwort übereinstimmen, und im zweiten Beispiel werden die eingeschlossenen Anweisungen nur einmal eingerückt. Beachten Sie, wie Sie nach dem einen unangenehmen Einzug von vier Leerzeichen haben break;.
Kaz
Beachten Sie, dass die akzeptierte Antwort nur teilweise korrekt ist, wie Jacks Kommentar hervorhebt und einige Feinheiten übersieht, die ich in meiner Antwort anspreche.
Shafik Yaghmour
Genau wie zu Ihrer Information: In C (sogar C11) anstelle von C ++ können Sie keine Deklaration kennzeichnen. Sie gehören nicht zur syntaktischen Kategorie statement. In C ++ können Sie (eine Komponente der syntaktischen Kategorie statementist declaration statement).
Jonathan Leffler

Antworten:

195

Das {}bezeichnet einen neuen Bereichsblock .

Betrachten Sie das folgende sehr konstruierte Beispiel:

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

Sie erhalten einen Compilerfehler, da dieser xbereits im Bereich definiert ist.

Wenn Sie diese in ihren eigenen Teilbereich trennen, müssen Sie sie nicht mehr xaußerhalb der switch-Anweisung deklarieren .

switch (a)
{
    case 42: {
        int x = GetSomeValue();
        return a * x; 
    }
    case 1337: {
        int x = GetSomeOtherValue(); //OK
        return a * x; 
    }
}
Rotem
quelle
11
Tatsächlich erhalten Sie IMO einen Compilerfehler, selbst wenn Sie die zweite Deklaration der Variablen x überspringen.
Abhishek Bansal
1
Wenn Sie diesen Stil nicht mehr verwenden und große Blöcke in die switch-Anweisung einfügen, ist es unlesbar, den Fällen zu folgen. Ich halte die Aussagen lieber winzig.
Masoud
2
@MatthieuM. Ich weiß, dass MS Visual Studio 2010 das Verhalten hat, das Abhishek angibt: Es wird keine Variablendeklaration innerhalb eines Falls kompiliert (es sei denn, Sie verwenden geschweifte Klammern, um einen neuen Bereich in diesem Fall zu kennzeichnen). Ob das den Standards entspricht, weiß ich nicht.
KRyan
1
@KRyan: Das tut es nicht, aber es ist eine so viel sicherere Alternative, dass ich ihnen kaum die Schuld geben kann, dies durchzusetzen.
Matthieu M.
6
In Abschnitt 6.7 (3) des Standards (Nummerierung für den Entwurf von 2005) ist festgelegt, dass Sie keine Initialisierung überspringen können, sodass Sie keine Initialisierung in einem Fallblock haben können.
Jack Aidley
23

TL; DR

Die einzige Möglichkeit, eine Variable mit einem Intializer oder einem nicht trivialen Objekt in einem Fall zu deklarieren, besteht darin, einen Blockbereich mithilfe einer {}anderen Steuerungsstruktur einzuführen, die einen eigenen Bereich wie eine Schleife oder eine if-Anweisung hat .

Gory Details

Wir können sehen, dass Fälle nur beschriftete Anweisungen sind, wie die Beschriftungen, die mit einer goto- Anweisung verwendet werden ( dies wird im C ++ - Entwurf des Standardabschnitts 6.1 Beschriftete Anweisung behandelt ), und wir können aus Abschnitt 6.7Absatz 3 ersehen, dass das Überspringen einer Deklaration in vielen Fällen nicht zulässig ist , einschließlich solcher mit einer Initialisierung:

Es ist möglich, in einen Block zu übertragen, jedoch nicht so, dass Deklarationen bei der Initialisierung umgangen werden. Ein Programm , das springt 87 von einem Punkt , wo eine Variable mit einer Dauer dynamischer Speichers ist in ihrem Umfang nicht bis zu einem Punkt , wo sie in ihrem Umfang ist schlecht gebildet , wenn die variable skalare Typen hat, Klassentyp mit einer trivialen Standardkonstruktors und einem trivialen destructor, eine cv-qualifizierte Version eines dieser Typen oder ein Array eines der vorhergehenden Typen und wird ohne Initialisierer deklariert (8.5).

und bietet dieses Beispiel:

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
}

Beachten Sie, dass es hier einige Feinheiten gibt. Sie können an einer Skalardeklaration vorbei springen , die keine Initialisierung hat, zum Beispiel:

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

ist vollkommen gültig ( Live-Beispiel ). Wenn Sie in jedem Fall dieselbe Variable deklarieren möchten, benötigen sie natürlich jeweils einen eigenen Gültigkeitsbereich, aber dies funktioniert auch außerhalb von switch- Anweisungen auf dieselbe Weise. Dies sollte also keine große Überraschung sein.

In Bezug auf die Gründe, warum ein Sprung nach der Initialisierung nicht zulässig ist, liefert der Fehlerbericht 467, obwohl er ein etwas anderes Problem abdeckt, einen vernünftigen Fall für automatische Variablen :

[...] automatische Variablen können, wenn sie nicht explizit initialisiert werden, unbestimmte ("Müll") Werte haben, einschließlich Trap-Darstellungen, [...]

Es ist wahrscheinlich interessanter, den Fall zu betrachten, in dem Sie einen Bereich innerhalb eines Switches auf mehrere Fälle erweitern. Das bekannteste Beispiel hierfür ist wahrscheinlich Duffs Gerät, das ungefähr so ​​aussehen würde:

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

Es ist eine Gewohnheit, mit der Sie Variablendeklarationen mit dem resultierenden Destruktor (oder Bereichskonflikten) in caseKlauseln einfügen können. Eine andere Sichtweise ist, dass sie für die Sprache schreiben, die sie sich gewünscht haben, wobei die gesamte Flusskontrolle aus Blöcken und nicht aus Folgen von Anweisungen besteht.

Yakk - Adam Nevraumont
quelle
4

Überprüfen Sie, ob dies eine grundlegende Einschränkung des Compilers ist, und Sie werden sich fragen, was passiert:

int c;
c=1;

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

    default : cout<<"def";
}

Dies gibt Ihnen einen Fehler:

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

Während dieser nicht wird:

int c;
c=1;

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

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

Die Verwendung von Klammern im Schalter kennzeichnet einen neuen Bereichsblock, wie von Rotem angegeben.

Aber es kann auch zur Klarheit beim Lesen sein. Zu wissen, wo der Fall aufhört, da Sie möglicherweise eine bedingte Unterbrechung haben.

Farbstoffe
quelle
0

Die Gründe könnten sein:

  1. Lesbarkeit, es verbessert jeden Fall visuell als Bereich Abschnitt.
  2. Deklarieren der verschiedenen Variablen mit demselben Namen für mehrere Switch-Fälle.
  3. Mikrooptimierungen - Bereich für eine wirklich teure Variable mit Ressourcenzuweisung, die Sie zerstören möchten, sobald Sie den Bereich des Falls verlassen, oder sogar ein Spaghetti-Szenario für die Verwendung des Befehls "GOTO".
Römischer Ambinder
quelle