C ++で不要な中括弧?


187

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

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からこれはかなり奇妙に思われ、波かっこがコードに含まれるべきだとは思いません。中かっこでコードを囲むことができる理由についてのすべての回答でいくつかの良い例を見ましたが、代わりにコードをメソッドに分けるべきではありませんか?


99
なぜ彼がそれをしたのかと尋ねたとき、あなたの同僚の答えは何でしたか?
Graham Borland

20
RAIIパターンと非常によく似ています。概要:c2.com/cgi/wiki?ResourceAcquisitionIsInitialization
Marcin

9
不要な中括弧が嫌いです
jacknad 2012年

8
内部ブロックに宣言はありましたか?
キーストンプソン、

15
多分彼は彼のエディタでその新しいセクションを簡単に「折りたたむ」ことを望んでいたのかもしれません
wim

回答:


281

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

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

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


37
+1は、新しい変数と古い習慣の明示的な言及
arne

46
+1は、リソースをできるだけ早く解放するために使用されるブロックスコープの使用
Leo

9
また、「if(0)」ブロックを簡単に実行できます。
vrdhn 2012年

コードレビュー担当者は、私が短すぎる関数/メソッドを書いているとよく言っています。それらを幸せに保ち、自分自身を幸せに保つ(つまり、無関係な懸念事項、変数の局所性などを分離する)ために、@ unwindで詳しく説明されているこのテクニックだけを使用します。
ossandcad 2012年

21
@ossandcad、彼らはあなたのメソッドが「短すぎる」とあなたに言っていますか?それは非常に難しいことです。開発者の90%(おそらく私も含まれています)は、反対の問題を抱えています。
Ben Lee

169

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


14
もちろん、実際には、そのブロックは別の関数にする必要があります。
BlueRaja-Danny Pflughoeft 2012年

8
歴史的注記:これは、ローカルの一時変数を作成できる初期のC言語の手法です。
Thomas Matthews

12
私は言わなければなりません-私の答えには満足していますが、それは本当にここでの最良の答えではありません。それが主な理由ですので、より良い答えは、明示的にRAIIを言及し、なぜあなたはデストラクタが特定のポイントで呼ばれるようにしたいと思います。これは「西側で最速の銃」の場合のようです。私は、十分な速さで投稿し、「勢い」を得て、いくつかの優れた回答よりも速く賛成票を獲得するのに十分な早期の賛成票を獲得しました。私が不満を言っているわけではありません!:-)
ruakh

7
@ BlueRaja-DannyPflughoeftあなたは単純化しすぎています。「別の関数に入れる」がすべてのコード問題の解決策ではありません。これらのブロックの1つにあるコードは、周囲のコードと密接に結合して、その変数のいくつかに影響を与える可能性があります。ポインター操作を必要とするC関数の使用。さらに、すべてのコードスニペットが再利用可能である(または再利用可能でなければならない)わけではなく、コード自体が意味をなさない場合もあります。C89でfor短命なものを作成するために、ステートメントをブロックすることがありますint i;。確かに、すべてforが別の関数にあるべきだと示唆しているのではありませんか?
AndersSjöqvist2014

101

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

私のプロダクションコードでは、次のようなコードを記述しています。

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つのスレッドだけで実行されます。


1
scoped_lock lock(mutex)//クリティカルセクションコード、次にlock.unlock()を使用するだけでクリーンだと思います。
シズル

17
@szielenski:クリティカルセクションのコードが例外をスローした場合はどうなりますか?ミューテックスは永久にロックされるか、コードはあなたが言ったほどクリーンではなくなります。
Nawaz

4
@Nawaz:@szielenskiのアプローチでは、例外が発生した場合にミューテックスがロックされたままになることはありません。彼はまたscoped_lock、例外で破壊されるa を使用します。私は通常、ロックの新しいスコープも導入することを好みますが、場合によってunlockは非常に便利です。たとえば、クリティカルセクション内で新しいローカル変数を宣言し、後でそれを使用します。(私は遅れていることを知っていますが、完全
ステファン

51

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

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

それは単に、特定の(副)目的を達成するコードのブロックを分離することです。単一のステートメントが希望する計算効果を実現することはまれです。通常は数回かかります。それらを(コメント付きで)ブロックに入れると、読者に(多くの場合、後で)伝えることができます。

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

例えば

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

私はそれをすべて行う関数を書くべきだと主張するかもしれません。1回だけ行う場合は、関数を記述しても構文とパラメーターが追加されるだけです。少しポイントがあるようです。これをパラメーターなしの匿名関数と考えてください。

運が良ければ、エディタにはブロックを隠すことさえできる折りたたみ/展開機能があります。

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


23

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


17

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

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

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

例1:

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

// ...

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

場合はmy_variable、特に良好であることを起こる互いから分離して使用される2つの異なる変数のために、明示的なスコープは、あなただけの名前の衝突を避けるために、新しい名前を発明しないようにすることができます。

これ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にとって重要になる可能性があります。


15

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


14

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

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

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

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

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

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


良い点が1つありますが、最近のコンパイラーは介入なしにこれを正しく行うと確信しています。(IIRC-少なくとも非組み込みコンパイラの場合-彼らは '99'までさかのぼって 'register'キーワードを無視しました。なぜなら彼らはいつもあなたよりも良い仕事をすることができるからです。)
Rup

11

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

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

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


10

「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

忙しい質問ですね 私は100アップをしたことがありません。この質問の何が特別なのですか?良いリンク。CはC ++よりも価値があります。
Wolfpack'08

5

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

  • 「スコーピング」の目的で(上記のように)
  • ある方法でコードを読みやすくする(を使用#pragmaするか、視覚化できる「セクション」を定義するのとほぼ同じ)
  • できるから。そのような単純な。

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


5

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

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

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

{
   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は常に返されるからです。


4

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


2

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

たとえば、いくつかの複雑なUIと多くのウィジェットがあり、それぞれに独自の間隔、レイアウトなどがあります。 space1, space2, spaceBetween, layout1, ...、コード。

まあ、いくつかはメソッドに分割する必要があると言うかもしれませんが、40の再利用できないメソッドを作成しても問題ありません。そのため、それらの前に中括弧とコメントを追加するだけなので、論理ブロックのように見えます。例:

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

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

多くの人が独自のコンポーネントをUIに追加し、一部のメソッドが非常に大規模になったときにこれらの問題が発生しましたが、すでにめちゃくちゃになっているクラス内に40の使い捨てのメソッドを作成することは現実的ではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.