1. ホーム
  2. c++

[解決済み] ループ内での変数宣言、グッドプラクティスかバッドプラクティスか?

2022-03-15 15:48:17

質問

質問1: ループの中で変数を宣言することはグッドプラクティスですか、バッドプラクティスですか?

他のスレッドで、パフォーマンスの問題があるかどうか(ほとんどの人がノーと言っています)、変数は常に使用される場所の近くで宣言するべきだという話を読みました。私が疑問に思っているのは、これを避けるべきか、あるいは実際に好ましいかということです。

for(int counter = 0; counter <= 10; counter++)
{
   string someString = "testing";

   cout << someString;
}

質問その2です。 ほとんどのコンパイラは、変数がすでに宣言されていることを認識して、その部分をスキップするのでしょうか、それとも実際に毎回メモリ内にその変数のためのスポットを作成するのでしょうか?

解決方法は?

これは エクセレント を実践しています。

ループの中に変数を作成することで、そのスコープがループの中に限定されることを保証します。ループの外から参照したり、呼び出したりすることはできません。

このように

  • 変数名が少し一般的なもの("i"など)であれば、コード内のどこかで同名の別の変数と混在するリスクはありません(quot;i"を使用して軽減することもできます)。 -Wshadow GCCの警告命令)

  • コンパイラは変数のスコープがループ内に限定されていることを認識しているので、誤って他の場所を参照した場合、適切なエラーメッセージを発行します。

  • 最後になりましたが、コンパイラはループの外では変数を使用できないことを知っているので、いくつかの専用の最適化(最も重要なのはレジスタの割り当て)をより効率的に実行することができるのです。例えば、後で再利用するために結果を保存する必要がありません。

要するに、やって正解なんです。

ただし、変数が その値を保持することは想定されていません。 ループのたびに そのような場合は、毎回初期化する必要があるかもしれません。また、ループを包含する大きなブロックを作成し、ループ間で値を保持しなければならない変数を宣言することだけを目的とすることもできます。このブロックには、通常、ループカウンタが含まれます。

{
    int i, retainValue;
    for (i=0; i<N; i++)
    {
       int tmpValue;
       /* tmpValue is uninitialized */
       /* retainValue still has its previous value from previous loop */

       /* Do some stuff here */
    }
    /* Here, retainValue is still valid; tmpValue no longer */
}

質問2について。 変数は、関数が呼び出されたときに一度だけ割り当てられます。実際、割り当ての観点からは、関数の先頭で変数を宣言するのと(ほぼ)同じです。唯一の違いはスコープで、変数はループの外では使用できません。この変数はループの外では使用できません。また、変数が割り当てられるのではなく、(スコープが終了した他の変数の)空きスロットを再利用するだけということもありえます。

スコープを制限し、より正確にすることで、より正確な最適化が可能になります。しかし、より重要なのは、コードの他の部分を読むときに心配する状態(つまり変数)が少なくなり、コードがより安全になることです。

このことは if(){...} ブロックを作成します。通常、:

    int result;
    (...)
    result = f1();
    if (result) then { (...) }
    (...)
    result = f2();
    if (result) then { (...) }

と書いた方が無難です。

    (...)
    {
        int const result = f1();
        if (result) then { (...) }
    }
    (...)
    {
        int const result = f2();
        if (result) then { (...) }
    }

この違いは、特にこのような小さな例では、些細なことに思えるかもしれません。 しかし、より大きなコードベースでは、これは役に立つでしょう。 result の値を f1() から f2() ブロックを作成します。それぞれの result は厳密に自分の範囲に限定されるため、その役割がより正確になります。レビュアーの立場からすると、彼の持つ 長距離状態変数 を心配し、追跡する必要があります。

コンパイラーも、より良い手助けをしてくれるでしょう : 将来、誤ってコードを変更した後を想定して。 result で適切に初期化されない場合 f2() . 2番目のバージョンは単純に動作を拒否し、コンパイル時に明確なエラーメッセージを表示します(実行時よりもずっと良い方法です)。最初のバージョンは何も表示されません。 f1() の結果と混同して、2回目のテストが行われるだけです。 f2() .

補足情報

オープンソースツール CppCheck (C/C++コードの静的解析ツール)は、変数の最適なスコープについて、いくつかの優れたヒントを提供しています。

アロケーションに関するコメントに対して。 上記のルールはC言語では正しいのですが、C++のクラスによってはそうではないかもしれません。

標準的な型や構造体では、変数のサイズはコンパイル時に分かっています。C言語には「構築」という概念がないため、関数が呼び出されると、変数の領域が(初期化なしで)単純にスタックに確保されます。そのため、ループ内で変数を宣言した場合のコストはゼロになります。

しかし、C++のクラスでは、コンストラクタというものがあり、私はあまりよく知りません。コンパイラは同じスペースを再利用するほど賢いので、おそらくアロケーションは問題にならないだろうが、初期化はループの反復ごとに行われる可能性が高い。