web-dev-qa-db-ja.com

ディメンション、インデックスなどのsize_tまたはint

C++では、_size_t_(または、より正確には_T::size_type_で、通常は_size_t_、つまりunsignedタイプ)がsize()、_operator[]_などへの引数など( _std::vector_ などを参照)

一方、.NET言語は同じ目的でint(およびオプションでlong)を使用します。実際、CLS準拠の言語は 符号なしの型をサポートする必要はありません です。

.NETがC++よりも新しいことを考えると、配列インデックスや長さのように負にできない可能性がある場合でも、 問題 が_unsigned int_を使用している可能性があることがわかります。 C++のアプローチは、下位互換性のための「歴史的成果物」ですか?または、2つのアプローチの間に実際の重要な設計上のトレードオフはありますか?

なぜこれが問題なのですか?まあ... C++の新しい多次元クラスには何を使用すればよいですか。 _size_t_またはint

_struct Foo final // e.g., image, matrix, etc.
{
    typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
    typedef size_t size_type; // c.f., std::vector<>

    dimension_type bar_; // maybe rows, or x
    dimension_type baz_; // e.g., columns, or y

    size_type size() const { ... } // STL-like interface
};
_
15
Ðаn

.NETがC++より新しいことを考えると、配列のインデックスや長さのように負にできない可能性がある場合でも、unsigned intを使用すると問題が発生する可能性があることがわかります。

はい。画像処理や配列処理などの特定のタイプのアプリケーションでは、現在の位置を基準に要素にアクセスする必要があることがよくあります。

sum = data[k - 2] + data[k - 1] + data[k] + data[k + 1] + ...

これらのタイプのアプリケーションでは、慎重に検討しないと、符号なし整数で範囲チェックを実行できません。

if (k - 2 < 0) {
    throw std::out_of_range("will never be thrown"); 
}

if (k < 2) {
    throw std::out_of_range("will be thrown"); 
}

if (k < 2uL) {
    throw std::out_of_range("will be thrown, without signedness ambiguity"); 
}

代わりに、範囲チェック式をrearrangeする必要があります。それが主な違いです。プログラマーは整数変換規則も覚えておく必要があります。疑わしい場合は、もう一度読んでください http://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions

多くのアプリケーションでは、非常に大きな配列インデックスを使用する必要はありませんが、範囲チェックを実行する必要があります。さらに、多くのプログラマーは、この式の再配置体操を行うように訓練されていません。 1つの機会を逃すと、悪用の扉が開きます。

C#は、配列ごとに2 ^ 31を超える要素を必要としないアプリケーション向けに設計されています。たとえば、スプレッドシートアプリケーションは、それほど多くの行、列、またはセルを処理する必要はありません。 C#は上限を処理します オプションのチェック演算 を使用して、コンパイラオプションをいじらずに、キーワード付きのコードブロックで有効にできます。このため、C#では符号付き整数の使用を推奨しています。これらの決定をまとめて考えると、理にかなっています。

C++は単純に異なり、正しいコードを取得するのが困難です。

符号付き算術が「最小驚きの原則」の潜在的な違反を取り除くことの実用的な重要性に関して、適切なケースはOpenCVです。OpenCVは、行列要素のインデックス、配列サイズ、ピクセルチャネルカウントなどに符号付き32ビット整数を使用します。処理は、相対配列インデックスを多用するプログラミングドメインの例です。符号なし整数アンダーフロー(負の結果の折り返し)は、アルゴリズムの実装を非常に複雑にします。

9
rwong

この答えは、誰がコードを使用するか、そして彼らが見たい標準に依存します。

size_tは、目的を持つ整数サイズです。

size_t型は、実装定義の符号なし整数型であり、任意のオブジェクトのバイト単位のサイズを含めるのに十分な大きさです。 (C++ 11仕様18.2.6)

したがって、バイト単位でオブジェクトのサイズを操作する場合は常に、_(SOMECODE] _を使用する必要があります。現在、多くの場合、これらのディメンション/インデックスを使用してバイトをカウントしていませんが、ほとんどの開発者は一貫性のためにsize_tを使用することを選択しています。

クラスがSTLクラスのルックアンドフィールを持つことを意図している場合は、常に常にsize_tを使用する必要があることに注意してください。すべて仕様内のSTLクラスのsize_tを使用します。コンパイラがtypedef size_tsize_tにすることは有効であり、unsigned intをtypedefedすることも有効ですunsigned longintまたはlongを直接使用すると、最終的にコンパイラーに遭遇し、クラスをSTLのスタイルに従っていると思った人が、標準に従っていないためにトラップされます。

符号付き型の使用に関しては、いくつかの利点があります。

  • 短い名前-intを入力するのは本当に簡単ですが、unsigned intを使用してコードを混乱させることははるかに困難です。
  • サイズごとに1つの整数-32ビットのCLS準拠の整数はInt32のみです。 C++には2つあります(int32_tおよびuint32_t)。これにより、APIの相互運用性がシンプルになります

署名付きの型の大きな欠点は明らかです。ドメインの半分が失われます。符号付きの数値は、符号なしの数値と同じ数に数えることはできません。 C/C++が登場したとき、これは非常に重要でした。 1つは、プロセッサの全機能に対処できるようにする必要があり、そのためには、符号なしの数値を使用する必要がありました。

.NETを対象とする種類のアプリケーションでは、フルドメインの署名されていないインデックスはそれほど必要ではありませんでした。このような数値の目的の多くは、マネージ言語では無効です(メモリプーリングが頭に浮かびます)。また、.NETが登場したとき、64ビットコンピューターは明らかに未来でした。 64ビット整数の全範囲が必要になるのは遠いので、1ビットを犠牲にすることは以前ほど苦痛ではありません。 40億のインデックスが本当に必要な場合は、64ビット整数の使用に切り替えるだけです。最悪の場合、32ビットマシンで実行すると少し遅くなります。

私はその取引を利便性の1つと見なしています。決して使用することのない少しのインデックスタイプを無駄にしても構わないほどの十分な計算能力がある場合は、intまたはlongと入力するだけで便利です。それから離れて歩きます。最後のビットが本当に欲しかった場合は、おそらく数値の符号に注意を払うべきでした。

14
Cort Ammon

上記のrwongの答え はすでに問題を非常に強調しています。

002を追加します。

  • _size_t_ 、つまり、...

    理論的に可能なあらゆるタイプのオブジェクト(配列を含む)の最大サイズを格納できます。

    ...は、sizeof(type)==1の場合、つまりバイト(char)型を処理する場合に範囲インデックスにのみ必要です。 (ただし、 ptrタイプよりも小さい場合があります

  • そのため、_xxx::size_type_は、符号付きサイズの型であっても99.9%のケースで使用できます。 (比較 _ssize_t_
  • _std::vector_と友人がサイズとインデックス付けのためにunsignedタイプの_size_t_を選択したという事実は consideredによって は設計上の欠陥である。私は同意します。 (真剣に、5分かけて LightningトークCppCon 2016:Jon Kalb「unsigned:A Guideline for Better Code」 を見てください。)
  • 今日C++ APIを設計するとき、あなたは狭い場所にいます:_size_t_を使用して標準ライブラリと整合するか、または(asigned_intptr_t_または_ssize_t_を使用すると、簡単でバグの発生しにくいインデックスの計算ができます。
  • Int32やint64を使用しないでください。署名してマシンのWordサイズが必要な場合は_intptr_t_を使用するか、_ssize_t_を使用してください。

質問に直接答えるために、アドレス空間の半分以上をアドレス指定する必要があるという理論上の問題が必須であるため、これは完全に「歴史的アーチファクト」ではありません be、aehm、C++のような低レベル言語でなんとかして対処しました。

後から考えて、私は個人的に、考えて、それはです標準ライブラリが署名なしで使用する設計上の欠陥_size_t_は、生のメモリサイズではなく、コレクションのように型指定されたデータの容量を表す場所であっても、どこにでもあります。

  • 与えられたC++の整数昇格規則->
  • 署名されていない型は、意味的に署名されていないサイズのようなものの「セマンティック」型の良い候補にはなりません。

繰り返します ジョンのアドバイス ここ:

  • サポートする操作のタイプを選択します(値の範囲ではありません)。 (※1)
  • APIで符号なしの型を使用しないでください。これはバグを隠し、メリットはありません。
  • 数量に「unsigned」を使用しないでください。(* 2)

(* 1)つまり、unsigned ==ビットマスク、それに対して数学を実行しないでください(ここで最初の例外が発生します-ラップするカウンターが必要な場合があります-これは符号なしの型である必要があります)。

(* 2)数量は、数えたり、計算したりすることを意味します。

4
Martin Ba

パフォーマンス上の理由から、通常はsize_tを使用することを追加しますensure誤った計算によりアンダーフローが発生するため、両方の範囲チェック(ゼロ以下とsize()以上)を1つに減らすことができます。

署名付き整数を使用:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

if (i < 0)
{
    //error
}

if (i > size())
{
    //error
}

符号なし整数を使用:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

/// This will underflow any number below zero, so that it becomes a very big *positive* number instead.
uint32_t asUnsigned = static_cast<uint32_t>(i);

/// We now don't need to check for below zero, since an unsigned integer can only be positive.
if (asUnsigned > size())
{
    //error
}
0
asger