Usando operadores bit a bit para booleanos em C++
-
09-06-2019 - |
Pergunta
Existe alguma razão para não usar os operadores bit a bit &, | e ^ para valores "bool" em C++?
Às vezes me deparo com situações em que desejo que exatamente uma das duas condições seja verdadeira (XOR), então apenas coloco o operador ^ em uma expressão condicional.Às vezes, também quero que todas as partes de uma condição sejam avaliadas, independentemente de o resultado ser verdadeiro ou não (em vez de curto-circuito), então uso & e |.Às vezes, também preciso acumular valores booleanos, e &= e |= podem ser bastante úteis.
Fiquei com algumas sobrancelhas levantadas ao fazer isso, mas o código ainda é significativo e mais limpo do que seria de outra forma.Existe alguma razão para NÃO usá-los para bools?Existem compiladores modernos que dão resultados ruins para isso?
Solução
||
e &&
são operadores booleanos e os integrados têm garantia de retornar true
ou false
.Nada mais.
|
, &
e ^
são operadores bit a bit.Quando o domínio de números em que você opera é apenas 1 e 0, então eles são exatamente iguais, mas nos casos em que seus booleanos não são estritamente 1 e 0 – como é o caso da linguagem C – você pode acabar com algum comportamento você não queria.Por exemplo:
BOOL two = 2;
BOOL one = 1;
BOOL and = two & one; //and = 0
BOOL cand = two && one; //cand = 1
Em C++, no entanto, o bool
tipo é garantido como sendo apenas um true
ou um false
(que convertem implicitamente para respectivamente 1
e 0
), portanto, essa postura é menos preocupante, mas o fato de as pessoas não estarem acostumadas a ver essas coisas no código é um bom argumento para não fazê-lo.Apenas diga b = b && x
e acabar com isso.
Outras dicas
Duas razões principais.Resumindo, considere cuidadosamente;pode haver uma boa razão para isso, mas se houver MUITO explícito em seus comentários, porque pode ser frágil e, como você mesmo diz, as pessoas geralmente não estão acostumadas a ver códigos como este.
Xor bit a bit! = Xor lógico (exceto 0 e 1)
Em primeiro lugar, se você estiver operando com valores diferentes false
e true
(ou 0
e 1
, como números inteiros), o ^
operador pode introduzir comportamento não equivalente a um xor lógico.Por exemplo:
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
}
Agradecemos ao usuário @Patrick por expressar isso primeiro.
Ordem de operações
Segundo, |
, &
, e ^
, como operadores bit a bit, não causam curto-circuito.Além disso, vários operadores bit a bit encadeados em uma única instrução - mesmo com parênteses explícitos - podem ser reordenados otimizando os compiladores, porque todas as três operações são normalmente comutativas.Isto é importante se a ordem das operações for importante.
Em outras palavras
bool result = true;
result = result && a() && b();
// will not call a() if result false, will not call b() if result or a() false
nem sempre dará o mesmo resultado (ou estado final) que
bool result = true;
result &= (a() & b());
// a() and b() both will be called, but not necessarily in that order in an
// optimizing compiler
Isto é especialmente importante porque você não pode controlar os métodos a()
e b()
, ou alguém pode aparecer e alterá-los mais tarde, sem entender a dependência, e causar um bug desagradável (e muitas vezes apenas de compilação de lançamento).
Eu penso
a != b
é o que você quer
As sobrancelhas levantadas devem dizer o suficiente para você parar de fazer isso.Você não escreve o código para o compilador, você o escreve primeiro para seus colegas programadores e depois para o compilador.Mesmo que os compiladores funcionem, surpreender outras pessoas não é o que você deseja - os operadores bit a bit são para operações bit, não para bools.
Suponho que você também coma maçãs com garfo?Funciona, mas surpreende as pessoas, então é melhor não fazer isso.
Desvantagens dos operadores de nível de bit.
Você pergunta:
“Existe alguma razão para não usar os operadores bit a bit
&
,|
, e^
para valores "bool" em C++?”
Sim o Operadores lógicos, esses são os operadores booleanos integrados de alto nível !
, &&
e ||
, oferecem as seguintes vantagens:
Garantido conversão de argumentos para
bool
, ou sejapara0
e1
valor ordinal.Garantido avaliação de curto-circuito onde a avaliação da expressão para assim que o resultado final é conhecido.
Isso pode ser interpretado como uma lógica de valor em árvore, com Verdadeiro, Falso e Indeterminado.Equivalentes textuais legíveis
not
,and
eor
, mesmo que eu não os use sozinho.
Como o leitor Antimony observa em um comentário, os operadores de nível de bit também possuem tokens alternativos, a saberbitand
,bitor
,xor
ecompl
, mas na minha opinião estes são menos legíveis do queand
,or
enot
.
Simplificando, cada vantagem dos operadores de alto nível é uma desvantagem dos operadores de nível de bit.
Em particular, como os operadores bit a bit não possuem conversão de argumento para 0/1, você obtém, por exemplo. 1 & 2
→ 0
, enquanto 1 && 2
→ true
.Também ^
, exclusivo bit a bit ou, pode se comportar mal dessa maneira.Considerados como valores booleanos 1 e 2 são iguais, ou seja, true
, mas considerados padrões de bits, eles são diferentes.
Como expressar lógico ou em C++.
Em seguida, você fornece um pouco de base para a pergunta,
“Às vezes me deparo com situações em que quero que exatamente uma das duas condições seja verdadeira (XOR), então apenas coloco o operador ^ em uma expressão condicional.”
Bem, os operadores bit a bit têm maior precedência do que os operadores lógicos.Isto significa, em particular, que numa expressão mista como
a && b ^ c
você obtém o resultado talvez inesperado a && (b ^ c)
.
Em vez disso, escreva apenas
(a && b) != c
expressando de forma mais concisa o que você quer dizer.
Para o argumento múltiplo ou não há operador C++ que faça o trabalho.Por exemplo, se você escrever a ^ b ^ c
do que isso não é uma expressão que diz “ou a
, b
ou c
é verdade".Em vez disso, diz: “Um número ímpar de a
, b
e c
são verdadeiros“, que pode ser 1 deles ou todos os 3…
Para expressar o geral ou/ou quando a
, b
e c
são do tipo bool
, apenas escreva
(a + b + c) == 1
ou, com nãobool
argumentos, converta-os em bool
:
(!!a + !!b + !!c) == 1
Usando &=
para acumular resultados booleanos.
Você elabora ainda mais,
“Às vezes também preciso acumular valores booleanos, e
&=
e|=?
pode ser bastante útil.”
Bem, isso corresponde a verificar se respectivamente todos ou qualquer condição é satisfeita, e lei de Morgan diz como passar de um para o outro.Ou sejavocê só precisa de um deles.Você poderia, em princípio, usar *=
como um &&=
-operador (pois como o bom e velho George Boole descobriu, o AND lógico pode muito facilmente ser expresso como multiplicação), mas acho que isso deixaria perplexos e talvez induziria em erro os mantenedores do código.
Considere também:
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;
}
Saída com Visual C++ 11.0 e g++ 4.7.1:
true false
A razão para a diferença nos resultados é que o nível de bits &=
não fornece uma conversão para bool
de seu argumento do lado direito.
Então, qual desses resultados você deseja para o uso do &=
?
Se o primeiro, true
, então defina melhor um operador (por exemplocomo acima) ou função nomeada, ou use uma conversão explícita da expressão do lado direito, ou escreva a atualização completa.
Ao contrário da resposta de Patrick, C++ não tem ^^
operador para realizar um curto-circuito exclusivo ou.Se você pensar sobre isso por um segundo, ter um ^^
operador não faria sentido de qualquer maneira:com exclusivo ou, o resultado sempre depende de ambos os operandos.No entanto, o alerta de Patrick sobre a não-bool
Os tipos "booleanos" funcionam igualmente bem quando comparados 1 & 2
para 1 && 2
.Um exemplo clássico disso é o Windows GetMessage()
função, que retorna um estado triplo BOOL
:diferente de zero, 0
, ou -1
.
Usando &
em vez de &&
e |
em vez de ||
não é um erro de digitação incomum; portanto, se você estiver fazendo isso deliberadamente, merece um comentário dizendo o porquê.
Patrick fez bons comentários e não vou repeti-los.No entanto, posso sugerir reduzir as instruções 'if' para um inglês legível sempre que possível, usando vars booleanos bem nomeados. Por exemplo, e isso está usando operadores booleanos, mas você também pode usar bit a bit e nomear os booleanos apropriadamente:
bool onlyAIsTrue = (a && !b); // you could use bitwise XOR here
bool onlyBIsTrue = (b && !a); // and not need this second line
if (onlyAIsTrue || onlyBIsTrue)
{
.. stuff ..
}
Você pode pensar que usar um booleano parece desnecessário, mas ajuda em duas coisas principais:
- Seu código é mais fácil de entender porque o booleano intermediário da condição 'if' torna a intenção da condição mais explícita.
- Se você estiver usando código não padrão ou inesperado, como operadores bit a bit em valores booleanos, as pessoas poderão ver muito mais facilmente por que você fez isso.
EDITAR:Você não disse explicitamente que queria as condicionais para declarações 'se' (embora isso pareça mais provável), essa foi a minha suposição.Mas minha sugestão de um valor booleano intermediário ainda permanece.
IIRC, muitos compiladores C++ avisarão ao tentar converter o resultado de uma operação bit a bit como um bool.Você teria que usar uma conversão de tipo para deixar o compilador feliz.
Usar uma operação bit a bit em uma expressão if serviria à mesma crítica, embora talvez não por parte do compilador.Qualquer valor diferente de zero é considerado verdadeiro, então algo como "se (7 e 3)" será verdadeiro.Este comportamento pode ser aceitável em Perl, mas C/C++ são linguagens muito explícitas.Acho que a sobrancelha de Spock é uma questão de diligência.:) Eu acrescentaria "== 0" ou "!= 0" para deixar perfeitamente claro qual era o seu objetivo.
Mas de qualquer forma, parece uma preferência pessoal.Eu executaria o código por meio de lint ou ferramenta semelhante e veria se ele também acha que é uma estratégia imprudente.Pessoalmente, parece um erro de codificação.
O uso de operações bit a bit para bool ajuda a economizar lógica de previsão de ramificação desnecessária pelo processador, resultante de uma instrução 'cmp' trazida por operações lógicas.
Substituir as operações lógicas por operações bit a bit (onde todos os operandos são bool) gera um código mais eficiente que oferece o mesmo resultado.Idealmente, a eficiência deve superar todos os benefícios de curto-circuito que podem ser aproveitados no pedido usando operações lógicas.
Isso pode tornar o código um pouco ilegível, embora o programador deva comentá-lo explicando as razões pelas quais isso foi feito.