Використання побітових операторів для булевих в 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 & 2→ 0, в той час як 1 && 2→ true. Крім того ^, порозрядний ексклюзив або може неправильно поводитись таким чином. Логічні значення 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.