int
の代わりにstd::size_t
をループやものに使用すべきかと思っています。例えば:
#include <cstdint>
int main()
{
for (std::size_t i = 0; i < 10; ++i) {
// std::size_t OK here? Or should I use, say, unsigned int instead?
}
}
一般的に、std::size_t
を使用する場合のベストプラクティスは何ですか?
良い経験則は、ループ条件で自然にstd::size_t
そのものであるものと比較する必要があるものすべてに適用されます。
std::size_t
は任意のsizeof
式のタイプであり、C++で任意のオブジェクト(配列を含む)の最大サイズを表現できることが保証されています。拡張により、任意の配列インデックスに対して十分な大きさであることが保証されるため、配列のインデックスによるループの自然なタイプです。
数を数えるだけの場合、その数を保持する変数の型、またはint
またはunsigned int
(十分な大きさの場合)を使用する方が自然かもしれません。マシンの自然なサイズ。
size_t
は、sizeof
演算子の結果タイプです。
配列のサイズまたはインデックスをモデル化する変数には、size_t
を使用します。 size_t
はセマンティクスを伝えます。これは、別の整数ではなく、バイト単位のサイズまたはインデックスを表すことをすぐに理解できます。
また、size_t
を使用してサイズをバイト単位で表すと、コードを移植しやすくなります。
size_t
タイプは、何かのsizeを指定するためのものです。たとえば、文字列の長さを取得してから各文字を処理するなど、使用するのが自然です。
for (size_t i = 0, max = strlen (str); i < max; i++)
doSomethingWith (str[i]);
doは、境界条件に注意する必要があります。これは、符号なしの型であるためです。通常、最大値は大きいので、上端の境界はそれほど重要ではありません(ただし、isそこに到達できます)。ほとんどの人は、そのようなことに対してint
を使用します。なぜなら、そのint
の容量を超えるほど十分に大きくなる構造体や配列を持つことはめったにないからです。
ただし、次のようなことに注意してください。
for (size_t i = strlen (str) - 1; i >= 0; i--)
これは、符号なしの値のラップ動作のために無限ループを引き起こします(コンパイラーがこれに対して警告するのを見たことがありますが)。これは次の方法でも軽減できます(理解するのは少し難しいですが、少なくともラッピングの問題は免れます)。
for (size_t i = strlen (str); i-- > 0; )
デクリメントを継続条件のチェック後の副作用にシフトすることにより、値before decrementの継続をチェックしますが、ループ内でデクリメントされた値を使用します(これが理由です)ループはlen .. 1
ではなくlen-1 .. 0
から実行されます。
定義により、size_t
はsizeof
演算子の結果です。 size_t
は、サイズを参照するために作成されました。
あなたが何かをする回数(あなたの例では10回)はサイズではないので、なぜsize_t
を使用するのですか? int
、またはunsigned int
は問題ありません。
もちろん、ループ内でi
を使用して行うことも重要です。たとえば、unsigned int
を受け取る関数に渡す場合は、unsigned int
を選択します。
いずれにせよ、暗黙的な型変換を避けることをお勧めします。 すべての型変換を明示的にする。
size_t
は、文字列の長さ、ポインタがとるバイト数など、アイテムのサイズディメンションを指定する非常に読みやすい方法です。プラットフォーム間でも移植可能です。64ビットと32ビットの両方が適切に動作することがわかります。システム関数とsize_t
-unsigned int
ではできないかもしれないこと(たとえば、いつunsigned long
を使用すべきか)
Cスタイルの配列のインデックス作成/カウントにはstd :: size_tを使用します。
STLコンテナーの場合、(たとえば)vector<int>::size_type
があります。これは、ベクトル要素のインデックス付けとカウントに使用する必要があります。
実際には、通常は両方とも符号なし整数ですが、特にカスタムアロケーターを使用する場合は保証されません。
間もなく、ほとんどのコンピューターは64ビットOSを備えた64ビットアーキテクチャになり、数十億の要素のコンテナーで動作するプログラムを実行します。次に、mustループインデックスとしてint
の代わりにsize_t
を使用する必要があります。そうでない場合、インデックスは32ビットシステムと64ビットシステムの両方で、2 ^ 32:th要素でラップアラウンドします。
未来に備えましょう!
ほとんどは決してない
32ビットシステムで2GBを超えるcharのベクトルが必要な場合。他のすべてのユースケースでは、符号なしのタイプを使用するよりも、符号付きのタイプを使用する方がはるかに安全です。
例:
std::vector<A> data;
[...]
// calculate the index that should be used;
size_t i = calc_index(param1, param2);
// doing calculations close to the underflow of an integer is already dangerous
// do some bounds checking
if( i - 1 < 0 ) {
// always false, because 0-1 on unsigned creates an underflow
return LEFT_BORDER;
} else if( i >= data.size() - 1 ) {
// if i already had an underflow, this becomes true
return RIGHT_BORDER;
}
// now you have a bug that is very hard to track, because you never
// get an exception or anything anymore, to detect that you actually
// return the false border case.
return calc_something(data[i-1], data[i], data[i+1]);
size_t
と同等の署名は、int
ではなくptrdiff_t
です。ただし、ほとんどの場合、int
を使用するほうが、size_tよりもはるかに優れています。 ptrdiff_t
は、32ビットおよび64ビットシステムではlong
です。
これは、std :: containersを操作するたびに、size_tとの間で常に変換する必要があることを意味します。これはあまり美しくありません。しかし、進行中のネイティブ会議で、c ++の作者は、署名されていないsize_tでstd :: vectorを設計するのは間違いだと述べました。
コンパイラーがptrdiff_tからsize_tへの暗黙的な変換に関する警告を表示する場合、コンストラクター構文で明示的にすることができます。
calc_something(data[size_t(i-1)], data[size_t(i)], data[size_t(i+1)]);
境界チェックなしでコレクションを繰り返したい場合は、以下に基づいて範囲を使用します:
for(const auto& d : data) {
[...]
}
going native のBjarne Stroustrup(C++作成者)からの言葉
一部の人々にとって、STLのこの署名された/署名されていない設計エラーは、std :: vectorを使用せず、代わりに独自の実装を使用するのに十分な理由です。
Size_tを使用する場合は、次の式に注意してください
size_t i = containner.find("mytoken");
size_t x = 99;
if (i-x>-1 && i+x < containner.size()) {
cout << containner[i-x] << " " << containner[i+x] << endl;
}
Xの値に関係なく、if式でfalseになります。問題の原因を理解するのに数分しかかかりませんが、これを実現するのに数日かかりました(コードは非常に単純なので、単体テストは行いませんでした)。キャストを実行するか、ゼロを使用することをお勧めします。
if ((int)(i-x) > -1 or (i-x) >= 0)
両方の方法が機能するはずです。これが私のテスト実行です
size_t i = 5;
cerr << "i-7=" << i-7 << " (int)(i-7)=" << (int)(i-7) << endl;
出力:i-7 = 18446744073709551614(int)(i-7)=-2
他のコメントをお願いします。
size_tは、さまざまなライブラリによって返され、そのコンテナのサイズがゼロ以外であることを示します。あなたが一度戻ってきたときにそれを使用します:0
ただし、上記の例では、size_tでのループは潜在的なバグです。以下を考慮してください。
for (size_t i = thing.size(); i >= 0; --i) {
// this will never terminate because size_t is a typedef for
// unsigned int which can not be negative by definition
// therefore i will always be >= 0
printf("the never ending story. la la la la");
}
符号なし整数を使用すると、この種の微妙な問題が発生する可能性があります。したがって、私はsize_tを必要とするコンテナ/タイプと対話する場合にのみsize_tを使用することを好みます。