Використання побітових операторів для булевих в C ++


84

Чи є причина не використовувати побітові оператори &, | та ^ для значень "bool" у C ++?

Я іноді стикаюся з ситуаціями, коли я хочу, щоб саме одна з двох умов була істинною (XOR), тому я просто вкидаю оператор ^ у умовний вираз. Я також іноді хочу, щоб усі частини умови були оцінені, чи результат є істинним чи ні (а не коротке замикання), тому я використовую & і |. Мені також потрібно іноді накопичувати булеві значення, і & = та | = може бути дуже корисним.

Я робив кілька піднятих брів, роблячи це, але код все одно значущий і чистіший, ніж був би в іншому випадку. Чи є якась причина НЕ використовувати їх для bools? Чи є сучасні компілятори, які дають для цього погані результати?


Якщо ви дізналися про C ++ достатньо багато часу після того, як задали це запитання, вам слід повернутися і не прийняти поточну відповідь та прийняти відповідь Патріка Джонмейера за правильне пояснення різниці короткого замикання.
кодетаку

Загальне зауваження, оскільки люди ставлять під сумнів, чи це гарна ідея. Якщо ви дбаєте про охоплення гілок модульними тестами, то зменшення гілок бажано. Для цього корисні побітові оператори.
qznc

Відповіді:


61

||і &&є логічними операторами, а вбудовані гарантовано повертаються trueабо false. Більш нічого.

|, &і ^є побітовими операторами. Коли домен чисел, яким ви оперуєте, дорівнює лише 1 і 0, тоді вони абсолютно однакові, але в тих випадках, коли ваші логічні значення не є строго 1 і 0 - як це відбувається з мовою C - у вас може вийти якась поведінка ти не хотів. Наприклад:

BOOL two = 2;
BOOL one = 1;
BOOL and = two & one;   //and = 0
BOOL cand = two && one; //cand = 1

Однак у C ++ boolтип гарантовано буде лише або a trueабо a false(які неявно перетворюються на відповідно 1і 0), тому з цієї позиції менше хвилюється, але той факт, що люди не звикли бачити подібні речі в коді робить вагомий аргумент, щоб цього не робити. Просто скажіть b = b && xі закінчите з цим.


в С ++ немає такого поняття, як логічний оператор xor ^^. Для обох аргументів як bools! = Робить те саме, але може бути небезпечним, якщо будь-який аргумент не є bool (як це було б використанням ^ для логічного xor). Не впевнений, як це було прийнято ...
Грег Роджерс

1
В ретроспективі це було якось тупо. У будь-якому випадку, змінив приклад на &&, щоб вирішити проблему. Клянусь, я використовував ^^ колись у минулому, але цього не повинно було бути.
Патрік

C має тип `` bool '', який з 10 років може становити лише 0 або 1. _Bool
Йоханнес Шауб - litb

16
Вони все одно не є однаковими навіть для пулів, тому що вони не проводять короткого замикання
aaronman

4
Так, я твердо впевнений, що це не повинно бути прийнятою відповіддю, доки ви не зміните "Коли домен чисел, на якому ви працюєте, дорівнює 1 і 0, тоді вони абсолютно однакові" на "Коли домен чисел, яким ви оперуєте, дорівнює 1 і 0, різниця лише в тому, що побітові оператори не замикають ". Перше твердження категорично неправильне, тому тим часом я підтримав цю відповідь, оскільки воно містить це речення.
кодетаку

32

Дві основні причини. Словом, уважно розгляньте; для цього може бути вагома причина, але якщо у Ваших коментарях є ДУЖЕ відверте, оскільки воно може бути крихким, і, як Ви самі говорите, люди зазвичай не звикли бачити такий код.

Побітове 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 за те, що він висловив це перше.

Порядок операцій

По- друге, |, &, і^ , як і Бітові оператори, які не коротке замикання. Крім того, кілька побітових операторів, об'єднаних в один оператор - навіть з явними дужками - можна перевпорядкувати, оптимізуючи компілятори, оскільки всі 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(), або хтось інший може прийти і змінити їх пізніше, не розуміючи залежності, і спричинити неприємну (і часто лише випуск-побудову) помилку.


Не зовсім справедливе порівняння, якщо ви кидаєте на `` bool '' в одному випадку, а не в іншому випадку ... `` Cast to bool '' в обох випадках - це те, що змусило б це працювати, і чому воно неміцне (тому що ви повинні це пам'ятати).
Грег Роджерс

Пояснення короткого замикання полягає в тому, чому це повинно бути прийнятою відповіддю; Відповідь Патріка (помилка ... інший Патрік, якого просто називають Патрік) абсолютно помилковий, кажучи: "Коли домен чисел, на якому ви працюєте, дорівнює 1 і 0, тоді вони абсолютно однакові"
codetaku

13

Я думаю

a != b

це те, що ви хочете


1
Це правда, +1. Але не існує присвоювальної версії цього оператора ( !==так би мовити), тому, якщо ви обчислюєте XOR послідовності boolзначень, вам потрібно було б записати acc = acc!= condition(i);в тіло циклу. Компілятор, напевно, може впоратися з цим так ефективно, як якщо б !==існував, але деякі можуть виявити, що це не виглядає приємно, і віддадуть перевагу альтернативі додавання булевих значень як цілих чисел, а потім перевірки, чи є сума непарною.
Марк ван Левен

10

Підняті брови повинні сказати вам достатньо, щоб перестати це робити. Ви не пишете код для компілятора, ви пишете його спочатку для своїх колег-програмістів, а потім для компілятора. Навіть якщо компілятори працюють, дивувати інших людей - це не те, що ви хочете - побітові оператори призначені для бітових операцій, а не для булів.
Припускаю, ви теж їсте яблука виделкою? Це працює, але це дивує людей, тому краще не робити цього.


3
Так, це має бути головною відповіддю. Дотримуйтесь принципу найменшого сюрпризу. Код, який робить незвичні речі, важче читати і розуміти, і код читається набагато частіше, ніж пишеться. Не використовуйте милі фокуси, подібні цьому.
Елофф,

1
Я тут, тому що мій колега використовував побітові оператори "і" для bool. І зараз я витрачаю свій час, намагаючись зрозуміти, чи правильно це чи ні. Якби він цього не зробив, я б зараз зробив щось корисніше -_-. Якщо ви цінуєте час своїх колег, будь ласка, не використовуйте побітові оператори для bool!
anton_rh

9

Недоліки операторів бітового рівня.

Ви запитаєте:

«Чи є якась - або причина не використовувати оператори побітового &, |і ^для" BOOL "значень в C ++? "

Так, логічні оператори , тобто вбудовані логічні оператори високого рівня !, &&і ||пропонують такі переваги:

  • Гарантоване перетворення аргументів у bool, тобто в 0і 1порядкове значення.

  • Гарантована оцінка короткого замикання, коли оцінка виразу припиняється, як тільки стане відомий кінцевий результат.
    Це можна інтерпретувати як логіку дерева-значення, з True , False та невизначеним .

  • Прочитані текстові еквіваленти not, andі orнавіть якщо я сам їх не використовую.
    Як читач Сурма зазначає в коментарі також bitlevel оператори мають альтернативні маркери, а саме bitand, bitor, xorі compl, але , на мою думку , це менш читаються , ніж and, orі not.

Простіше кажучи, кожна така перевага операторів високого рівня є недоліком операторів бітового рівня.

Зокрема, оскільки побітовим операторам не вистачає перетворення аргументів на 0/1, ви отримуєте, наприклад, 1 & 20, в той час як 1 && 2true. Крім того ^, порозрядний ексклюзив або може неправильно поводитись таким чином. Логічні значення 1 і 2 розглядаються однаково, а саме true, але розглядаються як бітові шаблони, вони різні.


Як висловити логічне або / або на C ++.

Потім ви надаєте трохи передумов для питання,

"Я іноді стикаюся з ситуаціями, коли я хочу, щоб саме одна з двох умов була істинною (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


Використання &=для накопичення булевих результатів.

Ви далі розробляєте,

"Мені також потрібно іноді накопичувати булеві значення, &=і це |=?може бути дуже корисно".

Ну, це відповідає перевірці, чи виконуються відповідно всі чи будь-які умови, а закон де Моргана підказує, як переходити від одного до іншого. Тобто вам потрібен лише один з них. Ви в принципі можете використовувати *=як &&=-оператора (адже, як виявив старий добрий Джордж Бул, логічне І дуже легко можна виразити як множення), але я думаю, що це заплутає і, можливо, введе в оману тих, хто підтримує код.

Розглянемо також:

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:

правда
помилковий

Причина різниці в результатах полягає в тому, що рівень рівня &=не забезпечує перетворення boolаргументу правої сторони.

Отже, який із цих результатів ви бажаєте використовувати &=?

Якщо перше, trueто краще визначте оператор (наприклад, як зазначено вище) або іменовану функцію, або використовуйте явне перетворення виразу праворуч, або напишіть оновлення повністю.


побітові оператори також мають еквіваленти тексту
Сурма

@Antimony: Я не зрозумів , що коментар на перший , але так, операції bitlevel альтернативні маркери bitand, bitor, xorі compl. Думаю, саме тому я використав кваліфікацію "читабельна". Звичайно, читабельність напр. compl 42Є суб’єктивною. ;-)
Вітаю та hth. - Альф

Наскільки я можу сказати, логічні оператори можуть бути перевантажені іншими типами аргументів (хоча вони, як правило, не є), тому перший пункт "гарантоване перетворення аргументів у bool" є лише наслідком домовленості, а не гарантією того, що мова С ++ насправді дає ти.
Марк ван Левен

@MarcvanLeeuwen: Можливо, ваша візуальна підсистема не помітила “вбудованих булевих операторів високого рівня!, && та ||”. Ви маєте рацію, що ці оператори можуть бути перевантажені, і тоді головна проблема полягає в тому, що семантика тонко змінюється, більше не оцінка короткого замикання. Але це проблема з перевантаженнями, а не з вбудованими операторами.
Вітаю і hth. - Альф

Правильно (мені це не вистачало). Але тоді твердження все одно вводить в оману, оскільки ви просто говорите, що ці оператори беруть логічні аргументи; якщо (і лише в тому випадку, якщо) вибрано вбудовані версії операторів, то компілятор вставить перетворення аргументів. Подібним чином вбудований (високорівневий) беззнаковий цілочисельний оператор додавання +гарантує перетворення своїх аргументів у unsigned, але це не заважає unsigned int n=-1; n+=3.14;насправді використовувати double(або це так float?) Операцію додавання. (Порівняйте свій &=приклад.)
Марк ван Левен

3

На відміну від відповіді Патріка, C ++ не має ^^оператора для виконання ексклюзивного короткого замикання або. Якщо замислитися над цим на секунду, наявність ^^оператора все одно не мало б сенсу: з ексклюзивним або результат завжди залежить від обох операндів. Однак попередження Патріка про не bool"логічні" типи виконується однаково добре у порівнянні 1 & 2з 1 && 2. Класичним прикладом цього є GetMessage()функція Windows , яка повертає три стани BOOL: ненульовий,, 0або -1.

Використання &замість &&і |замість ||не рідкісна помилка, тому, якщо ви навмисно це робите, це заслуговує на коментар із зазначенням чому.


6
^^Оператор повинен ще бути корисним , незалежно від розгляду короткого замикання. Це буде а) оцінювати операнди в логічному контексті і б) гарантувати повернення 1 або 0.
Крейг МакКвін

@CraigMcQueen. Це просто визначити inline bool XOR(bool a,bool b) { return a!=b; }і отримати те, що ви хочете, за винятком того, що це функція, а не оператор (інфікс). Або ви можете безпосередньо використовувати !=або перевантажувати якийсь інший оператор із цим значенням, але тоді, звичайно, вам потрібно бути дуже обережними, щоб випадково не закінчити використання ненавмисного однойменного перевантаження. І до речі ||і &&повернення trueабо false, ні 1чи 0.
Марк ван Левен

І мені здається , що той факт , що !, ||і &&оцінити їх аргументи в логічному контексті тільки тому , що ці оператори рідко перевантажені приймати інші типи; наскільки я можу сказати, мова дійсно допускає такі перевантаження, а отже, не гарантує оцінки аргументів у логічному контексті.
Марк ван Левен

2

Патрік зробив хороші моменти, і я не збираюся їх повторювати. Однак я міг би запропонувати скоротити оператори if якщо до читабельної англійської мови, де це можливо, використовуючи добре іменовані логічні символи vars. Наприклад, і це використовує логічні оператори, але ви можете однаково використовувати побітове і відповідне ім'я булів:

bool onlyAIsTrue = (a && !b); // you could use bitwise XOR here
bool onlyBIsTrue = (b && !a); // and not need this second line
if (onlyAIsTrue || onlyBIsTrue)
{
 .. stuff ..
}

Ви можете подумати, що використання логічної будови здається непотрібним, але це допомагає з двома основними речами:

  • Ваш код легше зрозуміти, оскільки проміжний логічний для умови "якщо" робить намір умови більш явним.
  • Якщо ви використовуєте нестандартний або несподіваний код, наприклад побітові оператори на булевих значеннях, люди можуть набагато легше зрозуміти, чому ви це зробили.

РЕДАГУВАТИ: Ви не прямо заявляли, що хочете умовні умови для тверджень "якщо" (хоча це здається найбільш ймовірним), це було моє припущення. Але моя пропозиція про проміжне логічне значення все ще існує.


1

Використання побітових операцій для bool допомагає заощадити непотрібну логіку прогнозування гілок процесором, що виникає в результаті інструкції 'cmp', введеної логічними операціями.

Заміна логічного на побітові операції (де всі операнди є bool) генерує більш ефективний код, що пропонує однаковий результат. Ефективність в ідеалі повинна переважати всі переваги короткого замикання, які можна використати при впорядкуванні за допомогою логічних операцій.

Це може зробити код нечитабельним, хоча програміст повинен коментувати його з мотивами, чому це було зроблено.


0

IIRC, багато компіляторів C ++ попереджатимуть при спробі передати результат побітової операції як bool. Вам потрібно було б використовувати типову команду, щоб зробити компілятор щасливим.

Використання побітової операції у виразі if служило б тій самій критиці, хоча, можливо, і не компілятором. Будь-яке ненульове значення вважається істинним, тому щось на зразок "if (7 & 3)" буде істинним. Така поведінка може бути прийнятною в Perl, але C / C ++ є дуже явними мовами. Я думаю, що брів Spock - це ретельність. :) Я додав би "== 0" або "! = 0", щоб було чітко зрозуміло, якою була ваша мета.

Але в будь-якому випадку це звучить як особисті уподобання. Я б запустив код через lint або подібний інструмент і перевірив, чи вважає він це також нерозумною стратегією. Особисто це читається як помилка кодування.


Ваш допис спонукав мене перевірити це, і gcc не попередив! Важко, тому що я збирався використати це попередження, щоб виправдати дозвіл мені бігти & = для накопичення результатів bool, вірячи, що інші побачать попередження, якщо згодом змінять мій тест. Мій код зазнав би невдачі навіть для цілих чисел, оскільки вони оцінюються як 0 / false - без попереджень! Час переробляти ...
мудрець
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.