C ++のブール値にビット演算子を使用する


84

C ++の「ブール」値にビット演算子&、|、および^を使用しない理由はありますか?

2つの条件のいずれか1つを真(XOR)にしたい状況に遭遇することがあるので、^演算子を条件式にスローします。また、条件のすべての部分を(短絡ではなく)結果が真であるかどうかを評価したい場合もあるので、&と|を使用します。また、ブール値を累積する必要がある場合もあります。&=と| =は非常に便利です。

これを行うと、眉毛が少し浮き上がりますが、コードはそれ以外の場合よりも意味があり、クリーンです。これらをboolsに使用しない理由はありますか?これに悪い結果をもたらす最新のコンパイラはありますか?


この質問をしてからC ++について十分に学んだ場合は、戻って現在の回答を受け入れず、短絡の違いを正しく説明するためのPatrickJohnmeyerの回答を受け入れる必要があります。
codetaku 2017

これが良い考えかどうか人々が質問するので、一般的な意見。単体テストのブランチカバレッジに関心がある場合は、ブランチを減らすことが望ましいです。これにはビット演算子が役立ちます。
qznc

回答:


61

||および&&はブール演算子であり、組み込み演算子はまたはのいずれ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型はいずれか一方のみであることが保証されるtruefalse(それぞれに暗黙的に変換された10、それはあまりこの立場からの心配のですので、)が、人々は、コードに、このようなものを見ることに使用されていないという事実それをしないための良い議論をします。ただ言ってb = b && x、それで終わらせてください。


C ++では論理xor演算子^^のようなものはありません。boolsとしての両方の引数に対して!=は同じことを行いますが、どちらかの引数がboolでない場合は危険な場合があります(論理xorに^を使用する場合など)。わからないこれが受け入れられてしまったか...
グレッグロジャース

1
振り返ってみると、それはちょっとばかげていました。とにかく、問題を修正するために例を&&に変更しました。私は過去に^^を使用したことを誓いますが、そうではなかったに違いありません。
パトリック

Cのブール型は10年前から0または1になります。_Bool
Johannes Schaub-litb 2008

16
ショートサーカットを行わないため、boolsでも同じではありません
aaronman 2013

4
ええ、「操作する数の定義域が1と0の場合、それらはまったく同じです」を「操作する数の定義域が1の場合」に変更するまで、これは受け入れられる答えではないと強く信じています。 0の場合、唯一の違いは、ビット演算子が短絡しないことです。」前者の発言はまったく間違っているので、その間、その文が含まれているので、私はこの回答に反対票を投じました。
codetaku 2017

32

2つの主な理由。要するに、慎重に検討してください。それには正当な理由があるかもしれませんが、コメントに非常に明確なものがある場合、それはもろくなり、あなた自身が言うように、人々は一般的にこのようなコードを見ることに慣れていません。

ビット単位のxor!=論理xor(0と1を除く)

まず、falseand true(または0and 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の功績。

操作の順序

第二に、|&、および^、ビット演算子として、短絡しないでください。さらに、3つの演算はすべて通常は可換であるため、1つのステートメントにチェーンされた複数のビット演算子は、明示的な括弧を使用しても、コンパイラーを最適化することで並べ替えることができます。これは、操作の順序が重要な場合に重要です。

言い換えると

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()、他の誰かがやって来て、依存関係を理解せずに後でメソッドを変更し、厄介な(多くの場合リリースビルドのみの)バグを引き起こす可能性があるためです。


あるケースでboolにキャストし、他のケースではキャストしない場合、実際には公平な比較ではありません...両方のケースでboolにキャストすると、それが機能し、壊れやすい理由になります(覚えておく必要があるため)。
グレッグロジャース

短絡の説明は、これが絶対に受け入れられる答えである理由です。パトリックの答え(えーと...パトリックという名前のもう一人のパトリック)は、「操作する数の定義域が1と0の場合、それらはまったく同じです」と言っているのは完全に間違っています
codetaku 2017

13

おもう

a != b

あなたが欲しいものです


1
これは本当です、+ 1。ただし、この演算子の割り当てバージョン(!==いわば)はないため、bool値のシーケンスのXORを計算する場合acc = acc!= condition(i);は、ループ本体に書き込む必要があります。コンパイラーはおそらくこれを!==存在するのと同じくらい効率的に処理できますが、見栄えが悪く、ブール値を整数として追加し、合計が奇数かどうかをテストするという選択肢を好む人もいます。
マーク・ファン・ルーウェン2016

10

眉を上げると、それをやめるのに十分なことがわかります。コンパイラー用のコードを書くのではなく、最初に仲間のプログラマーのために、次にコンパイラー用にコードを書きます。コンパイラが機能していても、他の人を驚かせることはあなたが望むものではありません-ビット演算子はboolではなくビット演算用です。
りんごもフォークで食べていると思いますか?それは機能しますが、人々を驚かせるので、それをしない方が良いです。


3
はい、これが一番の答えになるはずです。驚き最小の原則に従ってください。異常なことをするコードは読みにくく、理解しにくく、コードは書かれているよりもはるかに頻繁に読み取られます。このようなかわいいトリックは使用しないでください。
eloff 2014

1
同僚がブール値にビット単位の「and」演算子を使用したため、ここにいます。そして今、私はこれが正しいかどうかを理解しようと時間を費やしています。もし彼がこれをしなかったら、私は今もっと便利なことをしているでしょう-_-。同僚の時間を重視する場合は、ブール値にビット演算子を使用しないでください。
anton_rh 2017

9

ビットレベル演算子のデメリット。

あなたが尋ねる:

「ビット演算子を使用しない何らかの理由がある&|^のために、 『C ++でのブール値』の値?」

はい、論理演算子で、ハイレベルのブール演算子を内蔵!&&および||、以下の利点があります。

  • 保証の引数の変換bool、すなわちへ01序数値。

  • 最終結果がわかるとすぐに式の評価が停止する、保証された短絡評価
    これは、TrueFalse、およびIndeterminateのツリー値ロジックとして解釈できます。

  • 読み取り可能なテキスト同等物notandおよびor私はそれらを自分で使用していない場合でも、。
    リーダーとしてのコメントでアンチモンノートもbitlevel事業者は、すなわち、代替トークンを持ってbitandbitorxorcompl、私の意見では、これらは、より読みにくくしているandornot

簡単に言えば、高レベルの演算子のこのような利点はそれぞれ、ビットレベルの演算子の欠点です。

特に、ビット演算子には0/1への引数変換がないため、たとえば1 & 20、while 1 && 2true。が得られます。また^、ビット単位の排他的論理和は、このように誤動作する可能性があります。ブール値1と2は同じ、つまりtrue、と見なされますが、ビットパターンと見なされます。


論理的な表現方法/またはC ++で。

次に、質問の背景を少し説明します。

「2つの条件のいずれか1つを真(XOR)にしたい状況に遭遇することがあるので、^演算子を条件式に挿入するだけです。」

そうですね、ビット演算子は論理演算子よりも優先されます。これは特に、次のような混合式では

a && b ^ c

おそらく予想外の結果が得られます a && (b ^ c)

代わりにただ書く

(a && b) != c

あなたが意味することをより簡潔に表現する。

複数の引数の場合、またはその両方の場合、ジョブを実行するC ++演算子がありません。たとえば、それa ^ b ^ cよりも書く場合、「またはabまたはc真である」という表現ではありません。代わりに、「奇数abおよびc真」ています。これは、そのうちの1つまたは3つすべてである可能性があります…

一般的な表現をするには、、、およびがタイプである場合a、次のように記述します。bcbool

(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、演算子(上記のように)または名前付き関数を定義するか、右側の式の明示的な変換を使用するか、更新を完全に記述します。


ビット単位の演算子にも同等のテキストがあります
Antimony 2014年

@Antimony:私が最初にそのコメントを理解していなかったが、はい、bitlevel操作は、代替トークンを持ってbitandbitorxorcompl。それが私が「読みやすい」という資格を使用した理由だと思います。もちろん、例えばの読みやすさcompl 42は主観的です。;-)
乾杯とhth。-アルフ2014年

私が知る限り、論理演算子他の引数タイプでオーバーロードされる可能性があります(通常はそうではありませんが)。したがって、最初のポイント「引数のbool「への変換の保証」は単なる慣例の結果であり、C ++言語が実際に与える保証ではありません。君は。
マーク・ファン・ルーウェン2016

@MarcvanLeeuwen:おそらく、ビジュアルサブシステムは「組み込みの高レベルブール演算子!、&&および||」に気付かなかったでしょう。これらの演算子がオーバーロードされる可能性があることは間違いありません。主な問題は、セマンティクスが微妙に変化し、短絡評価ではなくなることです。しかし、これは組み込み演算子ではなく、オーバーロードの問題です。
乾杯とhth。-アルフ

そうです(私はそれを見逃しました)。しかし、これらの演算子はブール引数を取ると言っているだけなので、このステートメントは依然として誤解を招く可能性があります。組み込みバージョンの演算子が選択された場合(そしてその場合のみ)、コンパイラーは引数の変換を挿入します。同様に、組み込み(高レベル)の符号なし整数加算演算子+は、引数のへの変換を保証しますがunsigned、それはunsigned int n=-1; n+=3.14;実際にdouble(またはそれはfloat?)加算演算を使用することを妨げません。(あなたの&=例を比較してください。)
マーク・ファン・ルーウェン2016

3

パトリックの答えとは反対に、C ++には^^排他的論理和の短絡を実行するための演算子がありません。少し考えてみると、^^演算子を使用しても意味がありません。排他的論理和を使用すると、結果は常に両方のオペランドに依存します。ただし、bool「ブール」以外の型に関するPatrickの警告は、と比較1 & 21 && 2た場合にも同様に当てはまります。この典型的な例の1つは、非ゼロ、、、またはのGetMessage()トライステートを返すWindows関数です。BOOL0-1

&代わりに&&および|代わりに使用||することは珍しいタイプミスではないので、意図的にそれを行っている場合は、その理由をコメントする価値があります。


6
^^オペレータはまだかかわらず、短絡考慮有用であろう。それは)1または0を返すように真偽値およびb)保証にオペランドを評価します
クレイグ・マックイーン

@CraigMcQueen。inline bool XOR(bool a,bool b) { return a!=b; }(中置)演算子ではなく関数であることを除けば、必要なものを定義して取得するのは簡単です。または!=、この意味で他の演算子を直接使用またはオーバーロードすることもできますが、もちろん、同じ名前の意図しないオーバーロードを誤って使用しないように十分に注意する必要があります。そして、ちなみに||、または、ではなく、またはを&&返します。truefalse10
マーク・ファン・ルーウェン2016

そして、ブールコンテキストで引数を評価するという事実は!、これらの演算子が他の型を受け入れるためにオーバーロードされることはめったにないためだけであるようです。私が知る限り、言語はそのようなオーバーロードを許可しているため、ブールコンテキストでの引数の評価を保証するものではありません。||&&
マーク・ファン・ルーウェン2016

2

パトリックは良い点を挙げました、そして私はそれらを繰り返すつもりはありません。ただし、適切な名前のブール変数を使用して、可能な限り「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」ステートメントの条件が必要だと明示的に言っていませんでした(これは最も可能性が高いようですが)、それが私の仮定でした。しかし、中間のブール値についての私の提案はまだ有効です。


1

boolにビット単位の演算を使用すると、論理演算によってもたらされる「cmp」命令の結果として生じる、プロセッサによる不要な分岐予測ロジックを節約できます。

論理演算をビット演算(すべてのオペランドがブール値である)に置き換えると、同じ結果を提供するより効率的なコードが生成されます。理想的には、効率は、論理演算を使用した順序付けで活用できるすべての短絡の利点を上回る必要があります。

これにより、プログラマーはコードをコメントする必要がありますが、コードが少し読みにくくなる可能性があります。


0

IIRC、多くのC ++コンパイラは、ビット演算の結果をブール値としてキャストしようとすると警告を表示します。コンパイラーを満足させるには、型キャストを使用する必要があります。

if式でビット演算を使用すると、コンパイラによるものではないかもしれませんが、同じ批判になります。ゼロ以外の値はすべて真と見なされるため、「if(7&3)」のような値が真になります。この動作はPerlで許容できる場合がありますが、C / C ++は非常に明示的な言語です。スポックの眉毛はデューデリジェンスだと思います。:)「== 0」または「!= 0」を追加して、目的が何であるかを完全に明確にします。

しかしとにかく、それは個人的な好みのように聞こえます。私はlintまたは同様のツールを使用してコードを実行し、それが賢明でない戦略であると考えているかどうかを確認します。個人的には、コーディングミスのように読めます。


あなたの投稿は私にこれをテストする動機を与えました、そしてgccは警告しませんでした!残念ながら、私はその警告を使用して、&=を実行してブール結果を蓄積することを正当化するつもりだったので、他の人が後で私のテストを変更した場合に警告が表示されると信じています。私のコードは、整数でも0 / falseと評価されるため、警告なしで失敗します。リファクタリングに時間...
セージ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.