5

最近、リファクタリングセッション中に、自分が書いたコードを調べていて、いくつかのことに気づきました。

  1. unsigned char[0-255]の間隔で値を強制するために使用される関数がありました。
  2. 使用される他の関数int、または関数内にステートメントをlong含むデータ型を使用して、値を有効な範囲にサイレントにクランプします。if
  3. クラスに含まれる値、および/または未知であるupper boundが既知で明確な非負の関数への引数として宣言された値は、データ型として宣言されました(lower boundまたは4,000,000,000を超える可能性に応じて)。unsignedintlongupper bound

矛盾は不安です。これは私が続けるべき良い習慣ですか?ロジックを再考し、適切な非通知クランプの使用intまたは使用に固執する必要がありますか?long

「適切」の使用に関する注意:signedデータ型を使用し、値が範囲外になったときに通知例外をスローする場合がありますが、これらはとのために予約されdivde by zeroていconstructorsます。

4

4 に答える 4

6

C および C++ では、符号付きおよび符号なしの整数型には特定の特性があります。

署名された型にはゼロからかけ離れた境界があり、それらの境界を超える操作の動作は未定義です (変換の場合は実装定義)。

符号なしの型にはゼロの下限とゼロから遠い上限があり、これらの境界を超える操作は静かにラップアラウンドします。

多くの場合、実際に必要なのは、操作がその範囲を超えたときの特定の動作 (飽和、エラーの通知など) を伴う特定の範囲の値です。署名された型も署名されていない型も、このような要件に完全に適しているわけではありません。また、符号付きと符号なしの型が混在する操作は混乱を招く可能性があります。このような操作のルールは言語によって定義されていますが、常に明確であるとは限りません。

下限がゼロであるため、符号なしの型は問題になる可能性があります。そのため、妥当な値 (上限にほど遠い) を使用した操作は、予期しない方法で動作する可能性があります。たとえば、次のようになります。

for (unsigned int u = 10; u >= 0; u --) {
    // ...
}

は無限ループです。

1 つのアプローチは、符号なしの表現を絶対に必要としないすべてのものに符号付きの型を使用し、必要な値を保持するのに十分な幅の型を選択することです。これにより、署名付き/未署名の混合操作の問題が回避されます。たとえば、Java では、署名されていない型をまったく持たないようにすることで、このアプローチを強制しています。(個人的には、その決定はやり過ぎだと思いますが、その利点は理解できます。)

もう 1 つの方法は、論理的に負になることのない値に符号なしの型を使用することです。アンダーフローする可能性のある式や、符号付きと符号なしの型が混在する式には十分注意してください。

(さらに別の方法として、希望する動作を正確に使用して独自の型を定義することもできますが、それにはコストがかかります。)

ジョン・サレーの答えが言うように、一貫性はおそらくあなたが取る特定のアプローチよりも重要です.

「これが正しい、あちらが間違っている」と答えられればいいのですが、実際にはそうではありません。

于 2012-05-12T22:52:41.010 に答える
3

最大の利点unsignedは、値が常に正であることをコードで文書化できることです。

unsigned の範囲外に出ることは通常意図的ではなく、署名されている場合と同じくらいのフラストレーションを引き起こす可能性があるため、実際には安全を確保することはできません。

unsigned char を使用して間隔 [0-255] の値を強制する関数がありました。

ラップアラウンドに依存している場合は、8 ビットを超える可能性があるuint8_tasを使用してください。unsigned char

他の関数では、関数内の if ステートメントで int または long データ型を使用して、値を暗黙的に有効な範囲にクランプしていました。

これは本当に正しい動作ですか?

クラスに含まれる値、および/または未知の上限を持つ関数の引数として宣言されたが、既知の明確な非負の下限がある値は、符号なしデータ型 (上限が 4,000,000,000 を超える可能性に応じて int または long) として宣言されました。 )。

4,000,000,000 という上限はどこから得たのですか? 境界は と の間にINT_MAXありINT_MINます ( も使用できますstd::numeric_limits。C++11 では、 を使用decltypeして、テンプレート/マクロにラップできる型を指定できます。

decltype(4000000000) x; // x can hold at least 4000000000
于 2012-05-12T21:41:06.737 に答える
2

おそらく、一貫性が最も重要であると主張するでしょう。ある方法を選んで正しく実行すれば、後で他の人があなたのしていることを簡単に理解できるようになります。正しく行うには、考慮すべきいくつかの問題があります。

まず、整数変数 n が有効な範囲内にあるかどうかを確認する場合、たとえば 0 から N と書くのが一般的です。

if ( n > 0 && n <= N ) ...

この比較は、n が符号付きの場合にのみ意味があります。n が符号なしの場合、負の値がラップアラウンドするため、0 未満になることはありません。上記を次のように書き換えることができます。

if ( n <= N ) ...

これに慣れていない人は、混乱して、あなたが間違ったことをしたと思うかもしれません。

次に、C++ では整数の型サイズが保証されないことに注意してください。したがって、何かを 255 で制限したい場合、unsigned char ではうまくいかないかもしれません。変数に特定の意味がある場合、それを示すために typedef にとって価値があるかもしれません。たとえば、size_t はメモリ アドレスと同じ幅の値です。つまり、配列で使用でき、32 ビットまたは 64 ビットのマシン上にあることを心配する必要はありません。型を使用している理由を明確に伝えるため、可能な限りそのような typedef を使用するようにしています。(配列にアクセスしているため、size_t。)

第三に、ラップアラウンドの問題に戻ります。無効な番号でどうしたいですか。unsigned char の場合、型を使用してデータをバインドすると、255 を超える値が入力されたかどうかを確認できなくなります。それは問題になる場合とそうでない場合があります。

于 2012-05-12T21:46:54.533 に答える
0

これは主観的な問題ですが、私の意見を述べます。

個人的には、実行しようとしている操作に指定された型がない場合、サイズとインデックスの IE std::size_t、特定のビット深度の uintXX_t など...負の値を使用する必要がない限り、デフォルトで unsigned になります。

したがって、正の値を強制するために使用する場合ではなく、signed機能を明示的に選択する必要があります。

これに加えて、境界が心配な場合は、オーバーフローしていないことを確認するために、独自の境界チェックを行う必要があります。

しかし、多くの場合、データ型は、それを適用する関数の戻り値の型を持つコンテキストによって決定されます。

于 2012-05-12T21:38:07.660 に答える