C++ でのブール値のビット演算子の使用
-
09-06-2019 - |
質問
C++ の「bool」値にビット演算子 &、|、^ を使用しない理由はありますか?
2 つの条件のうち 1 つだけを true (XOR) にしたい状況に遭遇することがあるので、条件式に ^ 演算子を投入するだけです。また、条件のすべての部分を (短絡的にではなく) 結果が true かどうかで評価したい場合もあるので、& と | を使用します。また、ブール値を蓄積する必要がある場合もありますが、&= と |= は非常に便利です。
これを行うと眉をひそめられることもありますが、それでもコードは意味があり、そうでない場合よりもきれいです。これらをブール値に使用しない理由はありますか?これに関して悪い結果をもたらす最新のコンパイラはありますか?
解決
||
そして &&
ブール演算子であり、組み込み演算子は次のいずれかを返すことが保証されています。 true
または false
. 。他には何もありません。
|
, &
そして ^
はビット単位の演算子です。操作する数値の領域が 1 と 0 だけの場合、それらはまったく同じですが、C 言語の場合のように、ブール値が厳密に 1 と 0 ではない場合には、何らかの動作が発生する可能性があります。あなたは望んでいませんでした。例えば:
BOOL two = 2;
BOOL one = 1;
BOOL and = two & one; //and = 0
BOOL cand = two && one; //cand = 1
ただし、C++ では、 bool
type は、次のいずれかのみであることが保証されます。 true
または false
(暗黙的にそれぞれに変換されます) 1
そして 0
) なので、この立場からはそれほど心配はありませんが、人々がコード内でそのようなものを見ることに慣れていないという事実は、それを行わないことの十分な議論になります。言うだけ b = b && x
そしてそれで終わりです。
他のヒント
主な理由は 2 つあります。つまり、慎重に検討してください。それには正当な理由がある可能性がありますが、コメントに非常に明確な記述がある場合は、脆弱になる可能性があり、あなた自身も言っているように、人々は一般にこのようなコードを見ることに慣れていないためです。
ビットごとの xor != 論理 xor (0 と 1 を除く)
まず、次の値以外の値を操作している場合、 false
そして true
(または 0
そして 1
, 、整数として)、 ^
演算子は、論理 XOR と同等ではない動作を導入する可能性があります。例えば:
int one = 1;
int two = 2;
// bitwise xor
if (one ^ two)
{
// executes because expression = 3 and any non-zero integer evaluates to true
}
// logical xor; more correctly would be coded as
// if (bool(one) != bool(two))
// but spelled out to be explicit in the context of the problem
if ((one && !two) || (!one && two))
{
// does not execute b/c expression = ((true && false) || (false && true))
// which evaluates to false
}
これを最初に表現したユーザー @Patrick の功績です。
操作の順序
2番、 |
, &
, 、 そして ^
, 、ビット演算子として、短絡しないでください。さらに、1 つのステートメント内で複数のビット単位の演算子を連鎖させた場合、明示的な括弧を使用した場合でも、コンパイラを最適化することで順序を変更できます。これは、通常、3 つの演算はすべて可換であるためです。これは、操作の順序が重要な場合に重要です。
言い換えると
bool result = true;
result = result && a() && b();
// will not call a() if result false, will not call b() if result or a() false
常に同じ結果 (または終了状態) が得られるとは限りません。
bool result = true;
result &= (a() & b());
// a() and b() both will be called, but not necessarily in that order in an
// optimizing compiler
メソッドを制御できない可能性があるため、これは特に重要です a()
そして b()
, または、他の誰かが依存関係を理解せずに後で変更し、厄介な (多くの場合リリースビルドのみの) バグを引き起こす可能性があります。
私は思う
a != b
それはあなたが望むものです
上がった眉を見れば、それをやめるべきであることが十分にわかるはずです。コンパイラ用のコードを作成するのではなく、最初に仲間のプログラマ用にコードを作成し、次にコンパイラ用にコードを作成します。たとえコンパイラが動作したとしても、他の人を驚かせることはあなたが望んでいることではありません。ビット演算子はビット演算用であり、ブール演算用ではありません。
あなたもリンゴをフォークで食べると思いますか?それは機能しますが、人々を驚かせるので、やらないほうがよいでしょう。
ビットレベル演算子の欠点。
あなたが尋ねる:
「ビット演算子を使用しない理由はありますか?
&
,|
, 、 そして^
C++の「bool」値の場合?」
はい 論理演算子, 、これは組み込みの高レベルのブール演算子です !
, &&
そして ||
, 、次の利点があります。
保証されています 引数の変換 に
bool
, 、つまりに0
そして1
序数値。保証されています 短絡評価 ここで、式の評価は、最終結果が判明するとすぐに停止します。
これは、ツリー値ロジックとして解釈できます。 真実, 間違い そして 不定.読みやすいテキストの同等物
not
,and
そしてor
, たとえ自分が使っていなくても。
読者の Antimony がコメントで指摘しているように、ビットレベル演算子には代替トークンもあります。bitand
,bitor
,xor
そしてcompl
, 、しかし私の意見では、これらは以下よりも読みにくいです。and
,or
そしてnot
.
簡単に言うと、高レベル演算子のこうした利点は、ビットレベル演算子の欠点となります。
特に、ビット単位の演算子には引数を 0/1 に変換できないため、次のようになります。 1 & 2
→ 0
, 、 その間 1 && 2
→ true
. 。また ^
, 、ビットごとの排他的論理和は、このように誤動作する可能性があります。ブール値 1 と 2 は同じとみなされます。つまり、 true
, 、しかし、ビットパターンとして見なされると、それらは異なります。
論理的な表現の仕方 どちらか/または C++で。
次に、質問の背景を少し説明します。
「2 つの条件のうち 1 つだけを true (XOR) にしたい状況に遭遇することがあるので、条件式に ^ 演算子を投入するだけです。」
さて、ビット単位の演算子には 優先順位が高い 論理演算子よりも。これは、特に次のような混合式において、
a && b ^ c
おそらく予想外の結果が得られます a && (b ^ c)
.
代わりにただ書いてください
(a && b) != c
言いたいことをより簡潔に表現します。
複数の引数の場合 どちらか/または その仕事を行う C++ オペレーターはありません。たとえば、次のように書くと、 a ^ b ^ c
それは「どちらか」という表現ではありません。 a
, b
または c
それは本当です」代わりに、「奇数の a
, b
そして c
それらのうち 1 つであるか、または 3 つすべてである可能性があります...
一般的などちらかまたはいつを表現するには a
, b
そして c
タイプのものです bool
, 、 書くだけ
(a + b + c) == 1
または、非bool
引数を次のように変換します bool
:
(!!a + !!b + !!c) == 1
使用する &=
ブール値の結果を蓄積します。
さらに詳しく説明すると、
「時々ブール値を蓄積する必要もありますが、
&=
そして|=?
かなり役に立つかもしれません。」
さて、これはそれぞれ 全て または どれでも 条件が満たされており、 ド・モルガンの法則 あるものから別のものに移動する方法を示します。つまり、必要なのはそのうちの 1 つだけです。原則として使用できます *=
として &&=
-演算子 (古き良きジョージ・ブールが発見したように、論理 AND は乗算として非常に簡単に表現できることを発見しました) しかし、それはコードの管理者を混乱させ、おそらく誤解を招くと思います。
以下も考慮してください。
struct Bool
{
bool value;
void operator&=( bool const v ) { value = value && v; }
operator bool() const { return value; }
};
#include <iostream>
int main()
{
using namespace std;
Bool a = {true};
a &= true || false;
a &= 1234;
cout << boolalpha << a << endl;
bool b = {true};
b &= true || false;
b &= 1234;
cout << boolalpha << b << endl;
}
Visual C++ 11.0 および g++ 4.7.1 での出力:
true false
結果に違いが生じる理由は、ビットレベルが異なるためです。 &=
への変換は提供されません bool
右側の引数の。
それで、あなたはこれらの結果のうちどれを使用したいと思いますか? &=
?
前者であれば、 true
, 、その後、演算子をより適切に定義します (例:上記と同様)または名前付き関数を使用するか、右側の式の明示的な変換を使用するか、更新を完全に記述します。
パトリックの答えに反して、C++ には何もありません。 ^^
短絡排他的論理和を実行する演算子。少し考えてみると、 ^^
とにかく演算子は意味をなさないでしょう:排他的論理和の場合、結果は常に両方のオペランドに依存します。しかし、パトリックの警告は、bool
「ブール」型は比較するときに同様に有効です。 1 & 2
に 1 && 2
. 。この典型的な例の 1 つは Windows です。 GetMessage()
トライステートを返す関数 BOOL
:ゼロ以外の、 0
, 、 または -1
.
使用する &
の代わりに &&
そして |
の代わりに ||
は珍しいタイプミスではないので、意図的にそうしているのであれば、その理由をコメントする価値があります。
パトリックは良い点を指摘しましたが、私はそれを繰り返すつもりはありません。ただし、適切な名前のブール変数を使用して、可能な限り「if」ステートメントを読みやすい英語に減らすことをお勧めします。たとえば、これはブール演算子を使用していますが、同様にビット単位を使用してブール値に適切な名前を付けることもできます。
bool onlyAIsTrue = (a && !b); // you could use bitwise XOR here
bool onlyBIsTrue = (b && !a); // and not need this second line
if (onlyAIsTrue || onlyBIsTrue)
{
.. stuff ..
}
ブール値を使用する必要はないと思われるかもしれませんが、ブール値を使用すると、主に次の 2 つの点で役立ちます。
- 「if」条件の中間ブール値によって条件の意図がより明確になるため、コードが理解しやすくなります。
- ブール値のビット単位演算子など、非標準または予期しないコードを使用している場合、ユーザーはなぜこれを行ったのかをより簡単に理解できます。
編集:「if」ステートメントの条件文が必要であるとは明示的に言っていませんでした(これは最も可能性が高いように思えますが)、それが私の仮定でした。しかし、中間のブール値に関する私の提案は依然として有効です。
IIRC では、多くの C++ コンパイラーは、ビット単位の演算の結果を bool としてキャストしようとすると警告を出します。コンパイラを満足させるには、型キャストを使用する必要があります。
if 式でビット単位の演算を使用すると、コンパイラによってではないかもしれませんが、同じ批判を受けることになります。ゼロ以外の値はすべて true とみなされ、「if (7 & 3)」のようなものが true になります。この動作は Perl では許容されるかもしれませんが、C/C++ は非常に明示的な言語です。スポックの眉毛はデューデリジェンスだと思います。:) 目的が何であるかを完全に明確にするために、「== 0」または「!= 0」を追加します。
しかし、いずれにせよ、それは個人的な好みのように聞こえます。lint または同様のツールを通じてコードを実行し、それが賢明ではない戦略であると判断されるかどうかを確認します。個人的には、コーディングミスのように思えます。
bool に対してビット単位の演算を使用すると、論理演算によってもたらされる「cmp」命令によって生じる、プロセッサによる不要な分岐予測ロジックを節約するのに役立ちます。
論理演算をビット単位の演算 (すべてのオペランドがブール値) に置き換えると、同じ結果が得られるより効率的なコードが生成されます。理想的には、効率は、論理演算を使用した順序付けで活用できるすべての短絡上の利点を上回る必要があります。
これによりコードが少し読みにくくなる可能性がありますが、プログラマはコードにその理由をコメントする必要があります。