web-dev-qa-db-ja.com

C ++で不要な中括弧?

今日、同僚のコードレビューを行ったときに、奇妙なことがわかりました。彼は新しいコードを次のような中括弧で囲っていました。

Constructor::Constructor()
{
   existing code

   {
      New code: do some new fancy stuff here
   }

   existing code
}

これからの結果は、もしあれば、何ですか?これを行う理由は何でしょうか?この習慣はどこから来たのですか?

編集:

入力と以下のいくつかの質問に基づいて、すでに回答をマークしているにもかかわらず、質問にいくつか追加する必要があると感じています。

環境は組み込みデバイスです。 C++の衣服に包まれた多くのレガシーCコードがあります。多くのC向けのC++開発者がいます。

コードのこの部分には重要なセクションはありません。コードのこの部分でしか見ていません。主要なメモリ割り当ては行われず、いくつかのフラグが設定され、少し調整されます。

中括弧で囲まれたコードは次のようなものです。

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

(コードを気にしないでください、ただ中括弧に固執してください...;))中括弧の後には、もう少し調整、状態チェック、および基本的なシグナリングがあります。

私はその人と話をしましたが、彼の動機は変数の範囲を制限したり、衝突を命名したり、私が実際に拾うことができなかった他の何かをすることでした。

私のPOVからこれはかなり奇妙に思え、中括弧がコード内にあるべきではないと思います。コードを中括弧で囲むことができる理由についてのすべての回答にいくつかの良い例がありましたが、代わりにコードをメソッドに分割してはいけませんか?

173
iikkoo

新しいスコープを提供し、新しい(自動)変数をより「きれいに」宣言できるので、ときどき素敵です。

C++これは、新しい変数をどこにでも導入できるため、それほど重要ではないかもしれませんが、おそらく習慣はCからのもので、C99までこれを行うことができませんでした。 :)

C++にはデストラクタがあり、スコープの終了時にリソース(ファイル、ミューテックスなど)を自動的に解放しておくと便利です。これは、メソッドの開始時に取得した場合よりも短い期間、共有リソースを保持できることを意味します。

266
unwind

考えられる目的の1つは、 制御変数の範囲 です。また、自動ストレージを備えた変数はスコープから外れると破棄されるため、デストラクタを他の方法よりも早く呼び出すこともできます。

167
ruakh

余分な中かっこは、中かっこ内で宣言された変数のスコープを定義するために使用されます。変数がスコープ外に出たときにデストラクタが呼び出されるように行われます。デストラクタでは、ミューテックス(またはその他のリソース)を解放して、他の人が取得できるようにします。

実動コードでは、次のように書きました。

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
}

ご覧のとおり、このように、scoped_lockは、関数内で同時に追加の波括弧を使用してスコープを定義できます。これにより、余分なブレースの外側のコードは複数スレッドで同時に実行できますが、ブレースの内側のコードは正確に1つのスレッドで同時に実行されます。

100
Nawaz

他の人が指摘しているように、新しいブロックは新しいスコープを導入し、周囲のコードの名前空間を破壊せず、必要以上にリソースを使用しない独自の変数でコードを書くことができます。

ただし、これには別の理由があります。

特定の(サブ)目的を達成するコードブロックを分離するだけです。単一のステートメントで目的の計算効果が得られることはまれです。通常、それはいくつかかかります。それらを(コメント付きで)ブロックに配置すると、読者に伝えることができます(多くの場合、後日自分自身)。

  • このチャンクには一貫した概念的な目的があります
  • ここに必要なすべてのコードがあります
  • そして、ここにチャンクについてのコメントがあります。

例えば.

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

あなたは私がそれをすべて行う関数を書くべきだと主張するかもしれません。一度だけ行う場合、関数を記述すると、追加の構文とパラメーターが追加されます。ほとんど意味がないようです。これは、パラメータのない匿名関数と考えてください。

運が良ければ、エディターにはブロックを非表示にする折りたたみ/展開機能があります。

私はこれをいつもしています。検査する必要があるコードの境界を知ることは大きな喜びであり、そのチャンクが私が望むものではない場合、行を見る必要がないことを知ることはさらに良いことです。

49
Ira Baxter

1つの理由は、新しい中括弧ブロック内で宣言された変数の有効期間がこのブロックに制限されていることです。思い浮かぶもう1つの理由は、お気に入りのエディターでコードの折りたたみを使用できるようにすることです。

23
arne

これはif(またはwhileなど)ブロックと同じで、withoutifだけです。つまり、制御構造を導入せずにスコープを導入します。

この「明示的なスコープ」は通常、次の場合に役立ちます。

  1. 名前の衝突を避けるため。
  2. usingをスコープします。
  3. デストラクタがいつ呼び出されるかを制御します。

例1:

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

// ...

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

my_variableが、互いに独立して使用される2つの異なる変数に対して特に優れたnameである場合、明示的なスコープにより、新しい名前の作成を避けることができます名前の衝突を避けるため。

これにより、意図しない範囲でmy_variableを誤って使用することを回避できます。

例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.
        // ...
    }

}

これが役立つ実用的な状況はまれであり、コードがリファクタリングの準備が整っていることを示している可能性がありますが、本当に必要な場合にはメカニズムが存在します。

例3:

{
    MyRaiiClass guard1 = ...;

    // ...

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

    // ...

} // ~MyRaiiClass for guard1 called.

これは [〜#〜] raii [〜#〜] の場合に重要です。リソースを解放する必要性が関数または制御構造の境界に自然に「落ちない」場合です。

16

これは、スコープ付きロックをマルチスレッドプログラミングのクリティカルセクションと組み合わせて使用​​する場合に非常に便利です。中かっこ(通常は最初のコマンド)で初期化されたスコープロックは、ブロックの最後でスコープ外になり、他のスレッドが再び実行できるようになります。

14
learnvst

他の人はすでにスコープ、RAIIなどの可能性を正しくカバーしていますが、組み込み環境について言及しているため、さらに1つの潜在的な理由があります。

開発者は、このコンパイラのレジスタ割り当てを信頼していないか、スコープ内の自動変数の数を一度に制限することでスタックフレームサイズを明示的に制御したいかもしれません。

ここでisInitはおそらくスタック上にあります:

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

中括弧を削除すると、isInitのスペースが再利用される可能性がある場合でも、スタックフレームに予約される場合があります。同様にローカライズされたスコープを持つ自動変数が多数あり、スタックサイズが制限されている場合、それが問題になる可能性があります。

同様に、変数がレジスタに割り当てられている場合、スコープ外に移動すると、レジスタが再利用可能になったという強力なヒントが得られます。中かっこありとなしで生成されたアセンブラを見て、これが本当の違いをもたらすかどうかを判断する必要があります(そして、この違いが本当に重要かどうかを調べるために、プロファイルします-またはスタックオーバーフローを監視します)。

12
Useless

他の人はすでにスコープをカバーしていると思うので、不必要なブレースも開発プロセスの目的に役立つ可能性があることに言及します。たとえば、既存の関数の最適化に取り組んでいるとします。最適化の切り替えや、特定の一連のステートメントに対するバグの追跡は、プログラマにとって簡単です。中括弧の前のコメントを参照してください。

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

この方法は、デバッグ、組み込みデバイス、個人コードなどの特定のコンテキストで役立ちます。

11
kertronux

「ruakh」に同意します。 Cのスコープのさまざまなレベルの適切な説明が必要な場合は、この投稿をチェックしてください。

Cアプリケーションのさまざまなレベルのスコープ

一般に、「ブロックスコープ」の使用は、関数呼び出しの存続期間を追跡する必要がない一時変数のみを使用する場合に役立ちます。さらに、利便性のために複数の場所で同じ変数名を使用できるようにするために使用する人もいますが、一般的には良い考えではありません。例えば:

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

この特定の例では、returnValueを2回定義しましたが、関数スコープではなくブロックスコープにあるため(つまり、関数スコープはint main(void)の直後にreturnValueを宣言します)、私はしません各ブロックは宣言されたreturnValueの一時インスタンスを無視するため、コンパイラエラーが発生します。

これが一般的に良いアイデアであるとは言えません(つまり、ブロック間で変数名を繰り返し再使用しないでください)が、一般的には、時間を節約し、関数全体にわたるreturnValueの値。

最後に、私のコードサンプルで使用されている変数のスコープに注意してください。

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
10
DevNull

それでは、なぜ「不要な」中括弧を使用するのでしょうか?

  • 「スコーピング」の目的(上記のとおり)
  • ある意味でコードを読みやすくする(#pragma、または視覚化できる「セクション」の定義)
  • できるから。そのような単純な。

追伸BADコードではありません。 100%有効です。だから、それはむしろ(珍しい)味の問題です。

5
Dr.Kameleon

編集でコードを表示した後、不要な括弧はおそらく(元のコーダービューで)if/thenの間に何が起こるかを100%明確にすると言うことができます。後でさらに行を追加します。括弧は、間違いを犯さないことを保証します。

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

上記がオリジナルで、「extras」を削除すると、次の結果になります。

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

その後、後の変更は次のようになります。

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

そして、もちろん、if/thenに関係なくisInitが常に返されるため、問題が発生します。

5
nycynik

オブジェクトは、スコープから外れると自動的に破棄されます...

4
user677656

別の使用例は、UI関連のクラス、特にQtです。

たとえば、複雑なUIと多くのウィジェットがあり、それぞれに独自の間隔、レイアウトなどがあります。名前を付ける代わりにspace1, space2, spaceBetween, layout1, ...コードの2〜3行のみに存在する変数の説明のない名前を省くことができます。

メソッドで分割する必要があると言う人もいるかもしれませんが、40個の再利用不可能なメソッドを作成しても問題ないようです。そのため、それらの前にブレースとコメントを追加することにしました。例:

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

これがベストプラクティスであるとは言えませんが、レガシーコードには適しています。

多くの人が独自のコンポーネントをUIに追加し、一部のメソッドが非常に巨大になったときにこれらの問題が発生しましたが、既に台無しになっているクラス内に40のワンタイム使用メソッドを作成することは実用的ではありません。

1
htzfun