Использование побитовых операторов для логических значений в C++

StackOverflow https://stackoverflow.com/questions/24542

  •  09-06-2019
  •  | 
  •  

Вопрос

Есть ли причина не использовать побитовые операторы &, | и ^ для значений «bool» в C++?

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

При этом я несколько удивился, но код по-прежнему имеет смысл и чище, чем был бы в противном случае.Есть ли причина НЕ использовать их для логических значений?Есть ли современные компиляторы, которые дают при этом плохие результаты?

Это было полезно?

Решение

|| и && являются логическими операторами, а встроенные гарантированно возвращают либо 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 тип гарантированно будет только либо true или false (которые неявно преобразуются в соответственно 1 и 0), так что такая позиция вызывает меньше беспокойства, но тот факт, что люди не привыкли видеть такие вещи в коде, является хорошим аргументом в пользу того, чтобы не делать этого.Просто скажи b = b && x и покончим с этим.

Другие советы

Две основные причины.Короче говоря, подумайте внимательно;для этого может быть веская причина, но если в ваших комментариях будет ОЧЕНЬ явно, потому что это может быть хрупким, и, как вы сами говорите, люди обычно не привыкли видеть такой код.

Побитовое исключающее ИЛИ != Логическое исключающее ИЛИ (кроме 0 и 1)

Во-первых, если вы оперируете значениями, отличными от false и true (или 0 и 1, как целые числа), ^ оператор может ввести поведение, не эквивалентное логическому исключающему ИЛИ.Например:

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 за то, что он первым высказал это.

Порядок действий

Второй, |, &, и ^, как побитовые операторы, не замыкаются.Кроме того, несколько побитовых операторов, объединенных в один оператор (даже с явными круглыми скобками), можно переупорядочить с помощью оптимизирующих компиляторов, поскольку все три операции обычно коммутативны.Это важно, если порядок операций имеет значение.

Другими словами

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

это то, что ты хочешь

Поднятые брови должны сказать вам достаточно, чтобы прекратить это делать.Вы не пишете код для компилятора, вы сначала пишете его для своих коллег-программистов, а затем для компилятора.Даже если компиляторы работают, удивлять других людей — это не то, что вам нужно — побитовые операторы предназначены для битовых операций, а не для логических значений.
Я полагаю, вы тоже едите яблоки вилкой?Это работает, но людей удивляет, поэтому лучше этого не делать.

Недостатки битовых операторов.

Ты спрашиваешь:

«Есть ли причина не использовать побитовые операторы &, |, и ^ для значений «bool» в C++?»

Да, логические операторы, это встроенные логические операторы высокого уровня. !, && и ||, предлагают следующие преимущества:

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

  • Гарантировано оценка короткого замыкания где вычисление выражения прекращается, как только становится известен окончательный результат.
    Это можно интерпретировать как логику древовидных значений, где Истинный, ЛОЖЬ и Неопределенный.

  • Читабельные текстовые эквиваленты not, and и or, даже если я сам ими не пользуюсь.
    Как отмечает читатель Сурьма в комментарии, у операторов битового уровня есть альтернативные токены, а именно: bitand, bitor, xor и compl, но, на мой взгляд, они менее читабельны, чем and, or и not.

Проще говоря, каждое такое преимущество операторов высокого уровня является недостатком операторов битового уровня.

В частности, поскольку у побитовых операторов отсутствует преобразование аргументов в 0/1, вы получаете, например, 1 & 20, пока 1 && 2true.Также ^, побитовое исключающее или, может вести себя таким образом.Считая логические значения 1 и 2 одинаковыми, а именно true, но если рассматривать их как битовые шаблоны, они разные.


Как выразить логику или/или в С++.

Затем вы предоставляете немного предыстории вопроса,

«Иногда я сталкиваюсь с ситуациями, когда хочу, чтобы ровно одно из двух условий было истинным (XOR), поэтому я просто добавляю оператор ^ в условное выражение».

Ну, побитовые операторы имеют более высокий приоритет чем логические операторы.Это означает, в частности, что в смешанном выражении, таком как

a && b ^ c

вы получите, возможно, неожиданный результат a && (b ^ c).

Вместо этого напишите просто

(a && b) != c

выражая более кратко то, что вы имеете в виду.

Для множественного аргумента или/или нет оператора C++, который бы выполнял эту работу.Например, если вы напишете a ^ b ^ c чем это не выражение, в котором говорится «либо a, b или c правда".Вместо этого там написано: «Нечетное число a, b и c верны», что может быть одним из них или всеми тремя…

Чтобы выразить общее или/или когда 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:

true
false

Причина разницы в результатах заключается в том, что уровень битов &= не обеспечивает преобразование в bool его аргумента правой части.

Итак, какого из этих результатов вы желаете от использования &=?

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

Вопреки ответу Патрика, в C++ нет ^^ оператор для выполнения короткого замыкания исключающего или.Если вы задумаетесь об этом на секунду, то, имея ^^ оператор в любом случае не имеет смысла:с исключающим или результат всегда зависит от обоих операндов.Однако предупреждение Патрика о недопустимостиbool «Логические» типы одинаково хорошо сохраняются при сравнении 1 & 2 к 1 && 2.Классическим примером этого является 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 ..
}

Вы можете подумать, что использование логического значения кажется ненужным, но оно помогает в двух основных вопросах:

  • Ваш код легче понять, поскольку промежуточное логическое значение для условия «if» делает смысл условия более явным.
  • Если вы используете нестандартный или неожиданный код, например побитовые операторы для логических значений, людям гораздо легче понять, почему вы это сделали.

РЕДАКТИРОВАТЬ:Вы явно не сказали, что вам нужны условные выражения для операторов «если» (хотя это кажется наиболее вероятным), это было мое предположение.Но мое предложение о промежуточном логическом значении остается в силе.

IIRC, многие компиляторы C++ выдают предупреждение при попытке привести результат побитовой операции к логическому значению.Вам придется использовать приведение типов, чтобы компилятор был доволен.

Использование побитовой операции в выражении if вызвало бы ту же критику, хотя, возможно, и не со стороны компилятора.Любое ненулевое значение считается истинным, поэтому истинным будет что-то вроде «если (7 и 3)».Такое поведение может быть приемлемым в Perl, но C/C++ — очень явные языки.Я думаю, что бровь Спока - это проявление должной осмотрительности.:) Я бы добавил «== 0» или «!= 0», чтобы было совершенно ясно, какова ваша цель.

Но в любом случае это звучит как личное предпочтение.Я бы пропустил код через lint или аналогичный инструмент и посмотрел, не считает ли он это неразумной стратегией.Лично это читается как ошибка кодирования.

Использование побитовых операций для bool помогает сохранить ненужную логику прогнозирования ветвей процессором, возникающую в результате инструкции «cmp», введенной логическими операциями.

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

Это может сделать код немного нечитабельным, хотя программисту следует прокомментировать его и указать причины, по которым это было сделано.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top