Unnötige geschweifte Klammern in C ++?

186

Als ich heute eine Codeüberprüfung für einen Kollegen durchführte, sah ich etwas Besonderes. Er hatte seinen neuen Code mit solchen geschweiften Klammern umgeben:

Constructor::Constructor()
{
   existing code

   {
      New code: do some new fancy stuff here
   }

   existing code
}

Was ist das Ergebnis davon, wenn überhaupt? Was könnte der Grund dafür sein? Woher kommt diese Gewohnheit?

Bearbeiten:

Aufgrund der Eingabe und einiger Fragen unten habe ich das Gefühl, dass ich einige zur Frage hinzufügen muss, obwohl ich bereits eine Antwort markiert habe.

Die Umgebung besteht aus eingebetteten Geräten. Es gibt viel alten C-Code in C ++ - Kleidung. Es gibt viele C ++ C-Entwickler.

In diesem Teil des Codes gibt es keine kritischen Abschnitte. Ich habe es nur in diesem Teil des Codes gesehen. Es werden keine größeren Speicherzuordnungen vorgenommen, nur einige gesetzte Flags und ein bisschen Twiddling.

Der Code, der von geschweiften Klammern umgeben ist, lautet wie folgt:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
}

(Kümmere dich nicht um den Code, halte dich einfach an die geschweiften Klammern ...;)) Nach den geschweiften Klammern gibt es noch ein bisschen Twiddling, Zustandsprüfung und grundlegende Signalisierung.

Ich sprach mit dem Typen und seine Motivation war es, den Umfang von Variablen, Namenskonflikten und anderen, die ich nicht wirklich erfassen konnte, einzuschränken.

Aus meiner Sicht erscheint dies ziemlich seltsam und ich denke nicht, dass die geschweiften Klammern in unserem Code enthalten sein sollten. Ich habe in allen Antworten einige gute Beispiele dafür gesehen, warum man Code mit geschweiften Klammern umgeben kann, aber sollten Sie den Code nicht stattdessen in Methoden unterteilen?

iikkoo
quelle
98
Was war die Antwort Ihres Kollegen, als Sie ihn fragten, warum er es getan hat?
Graham Borland
20
Sehr häufig mit dem RAII-Muster. Schnelle Übersicht: c2.com/cgi/wiki?ResourceAcquisitionIsInitialization
Marcin
9
Ich hasse unnötige geschweifte Klammern
Jacknad
8
Gab es irgendwelche Erklärungen im inneren Block?
Keith Thompson
15
Vielleicht wollte er diesen neuen Abschnitt in seinem Editor
einfach wegfalten

Antworten:

280

Es ist manchmal schön, da es Ihnen einen neuen Bereich gibt, in dem Sie neue (automatische) Variablen "sauberer" deklarieren können.

In C++dieser ist vielleicht nicht so wichtig , da Sie überall neue Variablen einführen können, aber vielleicht ist die Gewohnheit aus C, wo man nicht diese bis C99 tun konnte. :) :)

Da C++es Destruktoren gibt, kann es auch nützlich sein, Ressourcen (Dateien, Mutexe usw.) automatisch freizugeben, wenn der Bereich beendet wird, was die Dinge sauberer machen kann. Dies bedeutet, dass Sie eine gemeinsam genutzte Ressource für eine kürzere Dauer behalten können, als wenn Sie sie zu Beginn der Methode abgerufen hätten.

entspannen
quelle
37
+1 für die explizite Erwähnung neuer Variablen und alter Gewohnheiten
Arne
46
+1 für die Verwendung des Blockbereichs, um Ressourcen so schnell wie möglich
Leo
9
Es ist auch einfach, einen Block mit 'if (0)' zu versehen.
vrdhn
Meine Code-Reviewer sagen mir oft, dass ich zu kurze Funktionen / Methoden schreibe. Um sie bei Laune zu halten und mich selbst bei Laune zu halten (dh um nicht verwandte Bedenken, variable Lokalitäten usw. zu trennen), verwende ich genau diese Technik, wie sie von @unwind ausgearbeitet wurde.
Ossandcad
21
@ossandcad, sagen sie dir, dass deine Methoden "zu kurz" sind? Das ist extrem schwer zu machen. 90% der Entwickler (wahrscheinlich auch ich) haben das gegenteilige Problem.
Ben Lee
169

Ein möglicher Zweck ist die Steuerung des Variablenbereichs . Und da Variablen mit automatischer Speicherung zerstört werden, wenn sie den Gültigkeitsbereich verlassen, kann ein Destruktor auch früher aufgerufen werden, als dies sonst der Fall wäre.

Ruakh
quelle
14
Natürlich sollte dieser Block wirklich nur in eine separate Funktion umgewandelt werden.
BlueRaja - Danny Pflughoeft
8
Historischer Hinweis: Dies ist eine Technik aus der frühen C-Sprache, mit der lokale temporäre Variablen erstellt werden konnten.
Thomas Matthews
12
Ich muss sagen - obwohl ich mit meiner Antwort zufrieden bin, ist es hier wirklich nicht die beste Antwort; In den besseren Antworten wird RAII explizit erwähnt, da dies der Hauptgrund ist, warum ein Destruktor an einem bestimmten Punkt aufgerufen werden soll. Dies scheint ein Fall von "schnellster Waffe im Westen" zu sein: Ich habe schnell genug gepostet, dass ich genug frühe Upvotes erhalten habe, um "Schwung" zu gewinnen, um Upvotes schneller als einige bessere Antworten zu erhalten. Nicht dass ich mich beschwere! :-)
Ruakh
7
@ BlueRaja-DannyPflughoeft Du bist zu einfach. "Setzen Sie es in eine separate Funktion" ist nicht die Lösung für jedes Codeproblem. Der Code in einem dieser Blöcke ist möglicherweise eng mit dem umgebenden Code gekoppelt und berührt mehrere seiner Variablen. Für die Verwendung von C-Funktionen sind Zeigeroperationen erforderlich. Darüber hinaus ist (oder sollte) nicht jedes Code-Snippet wiederverwendbar, und manchmal macht der Code für sich genommen nicht einmal Sinn. Ich setze manchmal Blöcke um meine forAussagen, um eine kurzlebige int i;in C89 zu erstellen . Sicherlich schlagen Sie nicht vor, dass jeder forin einer separaten Funktion sein sollte?
Anders Sjöqvist
101

Die zusätzlichen Klammern werden verwendet, um den Umfang der in den Klammern deklarierten Variablen zu definieren. Dies geschieht so, dass der Destruktor aufgerufen wird, wenn die Variable den Gültigkeitsbereich verlässt. Im Destruktor können Sie einen Mutex (oder eine andere Ressource) freigeben, damit andere ihn erwerben können.

In meinem Produktionscode habe ich ungefähr Folgendes geschrieben:

void f()
{
   //some code - MULTIPLE threads can execute this code at the same time

   {
       scoped_lock lock(mutex); //critical section starts here

       //critical section code
       //EXACTLY ONE thread can execute this code at a time

   } //mutex is automatically released here

  //other code  - MULTIPLE threads can execute this code at the same time
}

Wie Sie sehen, können Sie auf diese Weise scoped_lock eine Funktion verwenden und gleichzeitig ihren Umfang mithilfe zusätzlicher geschweifter Klammern definieren. Dies stellt sicher, dass der Code außerhalb der zusätzlichen Klammern zwar von mehreren Threads gleichzeitig ausgeführt werden kann , der Code innerhalb der geschweiften Klammern jedoch jeweils von genau einem Thread ausgeführt wird .

Nawaz
quelle
1
Ich denke, es ist sauberer, nur zu haben: scoped_lock lock (mutex) // Code für kritische Abschnitte, dann lock.unlock ().
Brutzeln
17
@szielenski: Was ist, wenn der Code aus dem kritischen Abschnitt eine Ausnahme auslöst? Entweder wird der Mutex für immer gesperrt, oder der Code wird nicht so sauber sein, wie Sie gesagt haben.
Nawaz
4
@Nawaz: @ szielenskis Ansatz lässt den Mutex in Ausnahmen nicht gesperrt. Er verwendet auch eine scoped_lock, die bei Ausnahmen zerstört wird. Normalerweise ziehe ich es vor, auch einen neuen Bereich für das Schloss einzuführen, aber in einigen Fällen unlockist dies sehr nützlich. Zum Beispiel, um eine neue lokale Variable innerhalb des kritischen Abschnitts zu deklarieren und sie später zu verwenden. (Ich weiß, ich bin spät dran, aber der Vollständigkeit
Stephan
51

Wie andere bereits betont haben, führt ein neuer Block einen neuen Bereich ein, der es einem ermöglicht, ein Stück Code mit eigenen Variablen zu schreiben, die den Namespace des umgebenden Codes nicht in den Papierkorb werfen und keine Ressourcen länger als nötig verwenden.

Es gibt jedoch noch einen weiteren guten Grund dafür.

Es geht einfach darum, einen Codeblock zu isolieren, der einen bestimmten (Unter-) Zweck erreicht. Es ist selten, dass eine einzelne Aussage einen von mir gewünschten Recheneffekt erzielt. normalerweise dauert es mehrere. Wenn ich diese in einen Block (mit einem Kommentar) setze, kann ich dem Leser (oft ich selbst zu einem späteren Zeitpunkt) sagen:

  • Dieser Block hat einen kohärenten konzeptuellen Zweck
  • Hier ist der gesamte benötigte Code
  • Und hier ist ein Kommentar zum Stück.

z.B

{  // update the moving average
   i= (i+1) mod ARRAYSIZE;
   sum = sum - A[i];
   A[i] = new_value;
   sum = sum + new_value;
   average = sum / ARRAYSIZE ;  
}

Sie könnten argumentieren, ich sollte eine Funktion schreiben, um all das zu tun. Wenn ich es nur einmal mache, fügt das Schreiben einer Funktion nur zusätzliche Syntax und Parameter hinzu. es scheint wenig Sinn zu haben. Stellen Sie sich dies als eine parameterlose, anonyme Funktion vor.

Wenn Sie Glück haben, verfügt Ihr Editor über eine Funktion zum Falten / Entfalten, mit der Sie den Block sogar ausblenden können.

Ich mache das die ganze Zeit. Es ist eine große Freude, die Grenzen des Codes zu kennen, den ich überprüfen muss, und noch besser zu wissen, dass ich keine der Zeilen betrachten muss, wenn dieser Block nicht der ist, den ich möchte.

Ira Baxter
quelle
23

Ein Grund könnte sein, dass die Lebensdauer von Variablen, die im neuen geschweiften Klammernblock deklariert sind, auf diesen Block beschränkt ist. Ein weiterer Grund, der mir in den Sinn kommt, ist die Möglichkeit, die Codefaltung im bevorzugten Editor zu verwenden.

Arne
quelle
17

Dies ist dasselbe wie ein if(oder whileetc ..) Block, nur ohne if . Mit anderen Worten, Sie führen einen Bereich ein, ohne eine Kontrollstruktur einzuführen.

Dieses "explizite Scoping" ist normalerweise in folgenden Fällen nützlich:

  1. Um Namenskonflikte zu vermeiden.
  2. Zum Umfang using.
  3. Steuern, wann die Destruktoren aufgerufen werden.

Beispiel 1:

{
    auto my_variable = ... ;
    // ...
}

// ...

{
    auto my_variable = ... ;
    // ...
}

Wenn my_variablees sich um einen besonders guten Namen für zwei verschiedene Variablen handelt, die isoliert voneinander verwendet werden, können Sie durch explizites Scoping vermeiden, einen neuen Namen zu erfinden, um den Namenskonflikt zu vermeiden.

Auf diese Weise können Sie auch vermeiden, dass Sie versehentlich my_variableaus dem vorgesehenen Bereich heraus verwenden.

Beispiel 2:

namespace N1 { class A { }; }
namespace N2 { class A { }; }

void foo() {

    {
        using namespace N1;
        A a; // N1::A.
        // ...
    }

    {
        using namespace N2;
        A a; // N2::A.
        // ...
    }

}

Praktische Situationen, in denen dies nützlich ist, sind selten und weisen möglicherweise darauf hin, dass der Code für das Refactoring reif ist. Der Mechanismus ist jedoch vorhanden, falls Sie ihn jemals wirklich benötigen sollten.

Beispiel 3:

{
    MyRaiiClass guard1 = ...;

    // ...

    {
        MyRaiiClass guard2 = ...;
        // ...
    } // ~MyRaiiClass for guard2 called.

    // ...

} // ~MyRaiiClass for guard1 called.

Dies kann für RAII in Fällen wichtig sein, in denen die Notwendigkeit, Ressourcen freizugeben, natürlich nicht auf Grenzen von Funktionen oder Kontrollstrukturen "fällt".

Branko Dimitrijevic
quelle
15

Dies ist sehr nützlich, wenn Sie Sperren mit Gültigkeitsbereich in Verbindung mit kritischen Abschnitten in der Multithread-Programmierung verwenden. Ihre in den geschweiften Klammern initialisierte Sperre mit Gültigkeitsbereich (normalerweise der erste Befehl) wird am Ende des Blockendes nicht mehr angezeigt, sodass andere Threads wieder ausgeführt werden können.

lernen
quelle
14

Alle anderen haben die Möglichkeiten von Scoping, RAII usw. bereits korrekt behandelt. Da Sie jedoch eine eingebettete Umgebung erwähnen, gibt es einen weiteren möglichen Grund:

Möglicherweise vertraut der Entwickler der Registerzuordnung dieses Compilers nicht oder möchte die Stapelrahmengröße explizit steuern, indem er die Anzahl der automatischen Variablen im Gültigkeitsbereich gleichzeitig begrenzt.

Hier isInitwird wahrscheinlich auf dem Stapel sein:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
}

Wenn Sie die geschweiften Klammern herausnehmen, isInitkann im Stapelrahmen Platz für reserviert sein, auch wenn dieser möglicherweise wiederverwendet werden könnte: Wenn viele automatische Variablen mit ähnlich lokalisiertem Bereich vorhanden sind und Ihre Stapelgröße begrenzt ist, kann dies ein Problem sein.

Wenn Ihre Variable einem Register zugeordnet ist, sollte das Verlassen des Gültigkeitsbereichs einen starken Hinweis darauf liefern, dass das Register jetzt zur Wiederverwendung verfügbar ist. Sie müssten sich den Assembler ansehen, der mit und ohne geschweifte Klammern generiert wurde, um herauszufinden, ob dies einen echten Unterschied ausmacht (und ihn profilieren - oder auf Stapelüberlauf achten -, um festzustellen, ob dieser Unterschied wirklich wichtig ist).

Nutzlos
quelle
+1 guter Punkt, obwohl ich ziemlich sicher bin, dass moderne Compiler dies ohne Intervention richtig machen. (IIRC - zumindest für nicht eingebettete Compiler - ignorierten das Schlüsselwort 'register' bereits '99, weil sie immer einen besseren Job machen konnten als Sie.)
Rup
11

Ich denke, andere haben das Scoping bereits behandelt, daher werde ich erwähnen, dass unnötige Klammern auch im Entwicklungsprozess einen Zweck erfüllen könnten. Angenommen, Sie arbeiten an einer Optimierung einer vorhandenen Funktion. Das Umschalten der Optimierung oder das Verfolgen eines Fehlers auf eine bestimmte Folge von Anweisungen ist für den Programmierer einfach - siehe den Kommentar vor den geschweiften Klammern:

// if (false) or if (0) 
{
   //experimental optimization  
}

Diese Vorgehensweise ist in bestimmten Kontexten wie Debugging, eingebetteten Geräten oder persönlichem Code hilfreich.

Kertronux
quelle
10

Ich stimme "ruakh" zu. Wenn Sie eine gute Erklärung der verschiedenen Ebenen des Geltungsbereichs in C wünschen, lesen Sie diesen Beitrag:

Verschiedene Umfangsebenen in der C-Anwendung

Im Allgemeinen ist die Verwendung von "Block scope" hilfreich, wenn Sie nur eine temporäre Variable verwenden möchten, die Sie während der gesamten Lebensdauer des Funktionsaufrufs nicht verfolgen müssen. Darüber hinaus wird es von einigen Benutzern verwendet, sodass Sie denselben Variablennamen an mehreren Stellen verwenden können, obwohl dies im Allgemeinen keine gute Idee ist. z.B:

int unusedInt = 1;

int main(void) {
  int k;

  for(k = 0; k<10; k++) {
    int returnValue = myFunction(k);
    printf("returnValue (int) is: %d (k=%d)",returnValue,k);
  }

  for(k = 0; k<100; k++) {
    char returnValue = myCharacterFunction(k);
    printf("returnValue (char) is: %c  (k=%d)",returnValue,k);
  }

  return 0;
}

In diesem speziellen Beispiel habe ich returnValue zweimal definiert, aber da es sich nur um einen Blockbereich handelt, anstatt um einen Funktionsbereich (dh: Funktionsbereich würde beispielsweise returnValue direkt nach int main (void) deklarieren), tue ich dies nicht Erhalten Sie alle Compilerfehler, da jeder Block die deklarierte temporäre Instanz von returnValue nicht kennt.

Ich kann nicht sagen, dass dies im Allgemeinen eine gute Idee ist (dh Sie sollten Variablennamen wahrscheinlich nicht wiederholt von Block zu Block wiederverwenden), aber im Allgemeinen spart dies Zeit und Sie können vermeiden, dass Sie die verwalten müssen Wert von returnValue über die gesamte Funktion.

Beachten Sie abschließend den Umfang der in meinem Codebeispiel verwendeten Variablen:

int:  unusedInt:   File and global scope (if this were a static int, it would only be file scope)
int:  k:           Function scope
int:  returnValue: Block scope
char: returnValue: Block scope
Wolke
quelle
Beschäftigte Frage, Mann. Ich hatte noch nie 100 Ups. Was ist das Besondere an dieser Frage? Guter Link. C ist wertvoller als C ++.
Wolfpack'08
5

Warum also "unnötige" geschweifte Klammern verwenden?

  • Für "Scoping" -Zwecke (wie oben erwähnt)
  • Code in gewisser Weise lesbarer machen (ähnlich wie das Verwenden #pragmaoder Definieren von "Abschnitten", die visualisiert werden können)
  • Weil du es kannst. So einfach ist das.

PS Es ist kein schlechter Code; es ist 100% gültig. Es ist also eher eine Frage des (ungewöhnlichen) Geschmacks.

Dr.Kameleon
quelle
5

Nachdem ich den Code in der Bearbeitung angezeigt habe, kann ich sagen, dass die unnötigen Klammern (in der ursprünglichen Codiereransicht) wahrscheinlich zu 100% klar sind, was während des Wenn / Dann passieren wird, obwohl es jetzt nur eine Zeile ist Weitere Zeilen später, und die Klammern garantieren, dass Sie keinen Fehler machen.

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
   return -1;
}

Wenn das oben genannte Original war und das Entfernen von "Extras" zu folgenden Ergebnissen führen würde:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) 
     return isInit;
   return -1;
}

dann könnte eine spätere Änderung folgendermaßen aussehen:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) 
     CallSomethingNewHere();
     return isInit;
   return -1;
}

und das würde natürlich ein Problem verursachen, da jetzt isInit immer zurückgegeben wird, unabhängig vom if / then.

Nycynik
quelle
4

Objekte werden automatisch zerstört, wenn sie den Gültigkeitsbereich verlassen ...


quelle
2

Ein weiteres Anwendungsbeispiel sind UI-bezogene Klassen, insbesondere Qt.

Zum Beispiel haben Sie eine komplizierte Benutzeroberfläche und viele Widgets, von denen jedes seinen eigenen Abstand, sein eigenes Layout usw. hat. Anstatt sie zu benennen, können space1, space2, spaceBetween, layout1, ...Sie sich vor nicht beschreibenden Namen für Variablen schützen, die nur in zwei bis drei Zeilen von existieren Code.

Nun, einige mögen sagen, dass Sie es in Methoden aufteilen sollten, aber das Erstellen von 40 nicht wiederverwendbaren Methoden sieht nicht in Ordnung aus. Deshalb habe ich beschlossen, nur geschweifte Klammern und Kommentare vor ihnen einzufügen, damit es wie ein logischer Block aussieht. Beispiel:

// Start video button 
{ 
   <here the code goes> 
}
// Stop video button
{
   <...>
}
// Status label
{
   <...>
}

Ich kann nicht sagen, dass dies die beste Vorgehensweise ist, aber es ist gut für Legacy-Code.

Diese Probleme traten auf, als viele Leute ihre eigenen Komponenten zur Benutzeroberfläche hinzufügten und einige Methoden sehr umfangreich wurden, aber es ist nicht praktikabel, 40 Methoden für die einmalige Verwendung innerhalb der Klasse zu erstellen, die bereits durcheinander geraten sind.

htzfun
quelle