これは優れた実践です。
ループ内に変数を作成することで、スコープがループ内に制限されるようにします。ループの外で参照したり呼び出したりすることはできません。
こちらです:
変数の名前が少し「一般的」(「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 について: 関数が呼び出されると、変数は 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++ クラスの場合、私があまり知らないコンストラクターがあります。コンパイラは同じスペースを再利用するのに十分賢いはずなので、割り当てはおそらく問題にはならないと思いますが、ループの繰り返しごとに初期化が行われる可能性があります。