359

質問 #1:ループ内で変数を宣言することは良い習慣ですか、それとも悪い習慣ですか?

パフォーマンスの問題があるかどうかについて他のスレッドを読んだことがあります (ほとんどの人は「いいえ」と言っていました)。私が疑問に思っているのは、これを避けるべきかどうか、または実際に優先するかどうかです。

例:

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

   cout << someString;
}

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

4

9 に答える 9

448

これは優れた実践です。

ループ内に変数を作成することで、スコープがループ内に制限されるようにします。ループの外で参照したり呼び出したりすることはできません。

こちらです:

  • 変数の名前が少し「一般的」(「i」など) である場合、コードの後半のどこかで同じ名前の別の変数と混合するリスクはありません ( -WshadowGCC の警告命令を使用して軽減することもできます) 。

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

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

要するに、あなたはそれをするのが正しいです。

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

{
    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 について: 関数が呼び出されると、変数は 1 回割り当てられます。実際、割り当ての観点からは、関数の先頭で変数を宣言するのと (ほぼ) 同じです。唯一の違いはスコープです。変数はループ外では使用できません。変数が割り当てられず、(スコープが終了した他の変数から) 空きスロットを再利用する可能性さえあります。

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

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()れませんf2()

補足情報

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

割り当てに関するコメントへの回答: 上記の規則は C では当てはまりますが、一部の C++ クラスでは当てはまらない場合があります。

標準型と構造体の場合、変数のサイズはコンパイル時にわかります。C には「構築」のようなものはないため、関数が呼び出されると、変数のスペースは (初期化なしで) 単にスタックに割り当てられます。そのため、ループ内で変数を宣言するときのコストは「ゼロ」です。

ただし、C++ クラスの場合、私があまり知らないコンストラクターがあります。コンパイラは同じスペースを再利用するのに十分賢いはずなので、割り当てはおそらく問題にはならないと思いますが、ループの繰り返しごとに初期化が行われる可能性があります。

于 2011-10-31T20:57:37.890 に答える
34

一般に、非常に近くに保つことは非常に良い習慣です。

場合によっては、ループから変数を引き出すことを正当化するパフォーマンスなどの考慮事項があります。

あなたの例では、プログラムは毎回文字列を作成および破棄します。一部のライブラリは小さな文字列の最適化 (SSO) を使用するため、場合によっては動的割り当てを回避できます。

これらの冗長な作成/割り当てを避けたい場合は、次のように記述します。

for (int counter = 0; counter <= 10; counter++) {
   // compiler can pull this out
   const char testing[] = "testing";
   cout << testing;
}

または、定数を引き出すことができます:

const std::string testing = "testing";
for (int counter = 0; counter <= 10; counter++) {
   cout << testing;
}

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

変数が消費するスペースを再利用でき、ループから不変条件を引き出すことができます。const char 配列 (上記) の場合、その配列を引き出すことができます。ただし、コンストラクターとデストラクターは、オブジェクト ( などstd::string) の場合、反復ごとに実行する必要があります。の場合std::string、その「スペース」には、文字を表す動的割り当てを含むポインターが含まれます。したがって、この:

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

それぞれの場合に冗長コピーが必要であり、変数が SSO 文字数のしきい値を超えている場合 (および SSO が std ライブラリによって実装されている場合) は、動的割り当てと解放が必要になります。

これを行う:

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

繰り返しごとに文字の物理コピーが必要ですが、文字列を割り当てると、文字列のバッキング割り当てのサイズを変更する必要がないことが実装で認識されるため、フォームは 1 つの動的割り当てになる可能性があります。もちろん、この例ではそれを行いません (複数の優れた代替案が既に示されているため) が、文字列またはベクトルの内容が異なる場合は考慮することができます。

では、これらすべてのオプション (およびそれ以上) をどうしますか? コストを十分に理解し、いつ逸脱する必要があるかがわかるまで、デフォルトに非常に近い値にしてください。

于 2013-08-01T21:28:26.680 に答える
18

JeremyRR の質問に答えるために投稿しませんでした (既に回答済みのため)。代わりに、私は単に提案をするために投稿しました。

JeremyRR に対しては、次のようにすることができます。

{
  string someString = "testing";   

  for(int counter = 0; counter <= 10; counter++)
  {
    cout << someString;
  }

  // The variable is in scope.
}

// The variable is no longer in scope.

「if」、「for」、「ながら」など。

私のコードは Microsoft Visual C++ 2010 Express でコンパイルされたので、動作することはわかっています。また、変数が定義されているブラケットの外側で変数を使用しようとしたところ、エラーが発生したため、変数が「破棄」されたことがわかります。

ラベルのない大括弧がたくさんあるとコードがすぐに読めなくなる可能性があるため、この方法を使用するのが悪い習慣であるかどうかはわかりませんが、いくつかのコメントで問題が解決する可能性があります。

于 2015-04-08T17:11:45.300 に答える
1

むかしむかし (C++98 より前); 以下は壊れます:

{
    for (int i=0; i<.; ++i) {std::string foo;}
    for (int i=0; i<.; ++i) {std::string foo;}
}

i が既に宣言されているという警告が表示されます (foo は {} 内にスコープが設定されているため、問題ありませんでした)。これはおそらく、人々が最初にそれが悪いと主張する理由です。しかし、それはずっと前に真実ではなくなりました。

そのような古いコンパイラをまだサポートする必要がある場合 (Borland を使用している人もいます)、答えはイエスです。i をループから外すケースを作成できます。同じ変数を使用して複数のループを挿入しますが、正直なところ、コンパイラはまだ失敗しますが、問題が発生する場合はこれで十分です。

このような古いコンパイラをサポートする必要がなくなった場合は、メモリ使用量を最小限に抑えるだけでなく、変数を取得できる範囲を最小限に抑える必要があります。プロジェクトの理解を容易にします。これは、すべての変数をグローバルにしない理由を尋ねるようなものです。同じ議論が適用されますが、スコープが少し変わるだけです。

于 2021-02-19T22:12:13.157 に答える
0

上記のすべての回答が質問の非常に優れた理論的側面を提供するため、コードを垣間見ることができるので、これは非常に良い方法です.GEEKSFORGEEKSでDFSを解決しようとしていました.ループ外で整数を宣言するコードを解くと、最適化エラーが発生します..

stack<int> st;
st.push(s);
cout<<s<<" ";
vis[s]=1;
int flag=0;
int top=0;
while(!st.empty()){
    top = st.top();
    for(int i=0;i<g[top].size();i++){
        if(vis[g[top][i]] != 1){
            st.push(g[top][i]);
            cout<<g[top][i]<<" ";
            vis[g[top][i]]=1;
            flag=1;
            break;
        }
    }
    if(!flag){
        st.pop();
    }
}

ループ内に整数を入れると、正しい答えが得られます...

stack<int> st;
st.push(s);
cout<<s<<" ";
vis[s]=1;
// int flag=0;
// int top=0;
while(!st.empty()){
    int top = st.top();
    int flag = 0;
    for(int i=0;i<g[top].size();i++){
        if(vis[g[top][i]] != 1){
            st.push(g[top][i]);
            cout<<g[top][i]<<" ";
            vis[g[top][i]]=1;
            flag=1;
            break;
        }
    }
    if(!flag){
        st.pop();
    }
}

これは、@justin が 2 番目のコメントで言っていたことを完全に反映してい ます。試してみてください....あなたはそれを手に入れるでしょう.これが助けになることを願っています.

于 2020-01-07T19:37:16.127 に答える
-1

ループ内またはループ外での変数の宣言. これは JVM 仕様の結果です. しかし、ベスト コーディング プラクティスの名の下に, 可能な限り最小のスコープで変数を宣言することをお勧めします (この例では, ループ内にあり, これが唯一の変数が使用される場所)。最小のスコープでオブジェクトを宣言すると、可読性が向上します。ローカル変数のスコープは、常に可能な限り小さくする必要があります。あなたの例では、 str は while ループの外では使用されていないと思います。それ以外の場合は、 while ループ内で宣言することはコンパイルされないため、オプションではないため、質問をすることはありません。

Java のループの内側または外側で変数を宣言すると違いがありますか? Is this for(int i = 0; i < 1000; i++) { int 個々の変数のレベルでは、効率に大きな違いはありませんが、1000 個のループと 1000 個の変数を持つ関数がある場合 (悪いスタイルは気にしないでください)すべての変数のすべての寿命が重複するのではなく同じになるため、体系的な違いが生じる可能性があります。

for ループ内でのループ制御変数の宣言 for ループ内で変数を宣言する場合、覚えておくべき重要な点が 1 つあります。その変数のスコープは、for ステートメントが終了すると終了します。(つまり、変数のスコープは for ループに限定されます。) この Java の例は、宣言ブロックを使用して Java の For ループで複数の変数を宣言する方法を示しています。

于 2020-12-27T12:39:42.003 に答える