Можно ли использовать побитовые операторы вместо логических?
Побитовые операторы работают с битами, логические операторы оценивают булевы выражения. Если выражения возвращают bool
, Почему бы нам не использовать побитовые операторы вместо логических?
В этом примере я использую побитовое вместо логического:
#include <iostream>
int main(){
int age;
std::cin >> age;
if( (age < 0) | (age > 100) ) // eg: -50: 1 | 0 = 1
std::cout << "Invalid age!" << std::endl;
// if( (age < 0) || (age > 100) )
// std::cout << "Invalid age!" << std::endl;
return 0;
}
5 ответов:
Один из возможных ответов: оптимизация . Например:
Предположим, чтоif ((age < 0) | (age > 100))
age = -5
нет необходимости оценивать(age > 100)
, так как выполняется первое условие (-5<0
). Однако предыдущий код действительно вычислит выражение(age > 100)
, которое не является необходимым.С:
if ((age < 0) || (age > 100))
Только Первая часть будет оценена.
Примечание: Как @Lundin упоминается в комментариях, иногда
|
быстрее, чем||
из-за точность ветвления для второго варианта (и проблема неправильного предсказания). Поэтому в тех случаях, когда другое выражение настолько дешево, параметр | май будь быстрее. Таким образом, единственный способ узнать в этих случаях-это проверить код на целевой платформе.
Самый важный ответ заключается в том, чтобы избежать неопределенного поведения и ошибок:Вы можете представить себе этот код:
int* int_ptr = nullptr; if ((int_ptr != nullptr) & (*int_ptr == 5))
Этот код содержит неопределенное поведение. Однако, если вы замените
&
на&&
, неопределенное поведение больше не существует.
Предположим, что вы получили два значения, которые позволяют вам продолжить, только если они оба ненулевые.
int b = foo(1); // returns 0x1 int c = foo(2); // returns 0x2
Чем следующие условия приводят к следующему:
b && c == true
, в то время какb & c == 0
if (b && c) { // This block will be entered } if (b & c) { // This block won't }
Существует отчетливая разница между
В отличие от большинства других операторов языка, логический оператор||
и|
.||
явно определяет порядок вычисления. Первый операнд||
должен быть вычислен до второго. Вторую вообще не нужно оценивать.Это принципиально отличается от
|
, который ведет себя как большинство операторов: порядок вычисления не определен, и оба выражения будут вычислены. Даже в том случае, когда один операнд оказывается ненулевым, другой операнд все равно будет оцениваться на наличие побочных эффектов.Означает, что код, подобный
f1() || f2()
, всегда будет оценивать этот псевдокод:if(f1() != 0) { f2(); }
Тогда как
Это также означает, что утверждения типа "|| быстрее, чем |" наивны. Конечно, в случаеf1() | f2()
будет выполнять обе функции, в неопределенном порядке, который программист не может знать.||
второй операнд не обязательно вычисляется, но это происходит за счет ветви, а также ограничения на то, как компилятору разрешается переупорядочивать выражение. Какой оператор, как правило, быстрее, не очевидно.
Даже если вы достигнете того же результата с помощью битового оператора, лучше использовать логический оператор здесь из-за производительности.
В выражении
(age < 0) || (age > 100)
второе условие(age > 100)
будет вычислено только в том случае, если(age < 0)
равноfalse
. Для такого выражения компилятор выдает код типа:cmpl $0x0,-0x4(%rbp) js 1004010f9 <main+0x19> // <-- Skip right expr evaluation if left true cmpl $0x64,-0x4(%rbp) jle 100401100 <main+0x20>
||
не производит никакого дополнительного ветвления, чтобы иметь возможность пропустить второе вычисление выражения.
Ответ: да, вы можете. Вопрос в том, зачем вам это нужно? Я могу назвать несколько причин, по которым вы не должны:
- это может быть очень запутанным для других программистов.
Легко пропустить, что один из операндов не относится к типуbool
, что может привести к тонким ошибкам.- порядок вычисления операндов не определен.
Это нарушает правило короткого замыкания.Чтобы проиллюстрировать последний пункт:
bool f1() { cout << "f1" << endl; return true; } bool f2() { cout << "f2" << endl; return true; } int main() { if (f1() || f2()) { cout << "That was for ||" << endl; } if (f1() | f2()) { cout << "That was for |" << endl; } return 0; }
Это отпечатки пальцев:
Предполагая, чтоf1 That was for || f1 f2 That was for |
f2
может иметь значительные побочные эффекты (if (okToDestroyWorld && destroyWorld()) ...
), разница может быть огромной. Это не удивило бы программиста Java (где|
и||
фактически определены для булевых значений спецификацией языка), но это не обычная практика в C++. Я могу придумать только одну причину для использования побитового оператора для булевых чисел: если вам нужен XOR. Нет оператора^^
, поэтомуif (a ^ b)
прекрасно, пока обаa
иb
являютсяbool
и нет стороны задействованные эффекты.