Usar operadores bit a bit para booleanos en C ++

81

¿Hay alguna razón para no usar los operadores bit a bit &, | y ^ para valores "bool" en C ++?

A veces me encuentro con situaciones en las que quiero que se cumpla exactamente una de las dos condiciones (XOR), por lo que simplemente lanzo el operador ^ a una expresión condicional. A veces también quiero que se evalúen todas las partes de una condición si el resultado es verdadero o no (en lugar de un cortocircuito), así que uso & y |. También necesito acumular valores booleanos a veces, y & = y | = pueden ser bastante útiles.

Me han levantado algunas cejas al hacer esto, pero el código sigue siendo significativo y más limpio de lo que sería de otra manera. ¿Hay alguna razón para NO usarlos para bools? ¿Hay compiladores modernos que den malos resultados para esto?

Jay Conrod
fuente
Si ha aprendido lo suficiente sobre C ++ desde que hizo esta pregunta, debe regresar y no aceptar la respuesta actual y aceptar la respuesta de Patrick Johnmeyer para explicar correctamente la diferencia de cortocircuito.
codetaku
Observación general porque la gente se pregunta si es una buena idea. Si le preocupa la cobertura de sucursales de las pruebas unitarias, entonces es deseable reducir las sucursales. Los operadores bit a bit son útiles para esto.
qznc

Respuestas:

59

||y &&son operadores booleanos y se garantiza que los integrados devuelvan trueo false. Nada más.

|, &Y ^son a nivel de bits operadores. Cuando el dominio de los números en el que opera es solo 1 y 0, entonces son exactamente los mismos, pero en los casos en que sus valores booleanos no son estrictamente 1 y 0, como es el caso del lenguaje C, puede terminar con algún comportamiento no querías. Por ejemplo:

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

En C ++, sin embargo, boolse garantiza que el tipo sea solo a trueo a false(que se convierten implícitamente en respectivamente 1y 0), por lo que es menos preocupante desde esta postura, pero el hecho de que la gente no está acostumbrada a ver tales cosas en el código. es un buen argumento para no hacerlo. Solo dilo b = b && xy termina con eso.

Patricio
fuente
no existe el operador lógico xor ^^ en C ++. Para ambos argumentos como bools! = Hace lo mismo, pero puede ser peligroso si alguno de los argumentos no es bool (como sería usar ^ para un xor lógico). No estoy seguro de cómo se aceptó esto ...
Greg Rogers
1
En retrospectiva, eso fue un poco tonto. De todos modos, cambió el ejemplo a && para solucionar el problema. Juro que he usado ^^ alguna vez en el pasado, pero este no debe haber sido el caso.
Patrick
C tiene un tipo bool que solo puede ser 0 o 1 desde hace 10 años. _Bool
Johannes Schaub - litb
16
Todavía no son lo mismo incluso para los bools porque no hacen un cortocircuito
Aaronman
3
Sí, creo firmemente que esta no debería ser la respuesta aceptada hasta que cambie "Cuando el dominio de números en el que opera es solo 1 y 0, entonces son exactamente iguales" a "Cuando el dominio de números en el que opera es solo 1 y 0, la única diferencia es que los operadores bit a bit no cortocircuitan ". La declaración anterior es rotundamente incorrecta, por lo que, mientras tanto, he votado en contra de esta respuesta ya que contiene esa oración.
codetaku
32

Dos razones principales. En resumen, considere cuidadosamente; podría haber una buena razón para ello, pero si hay algo MUY explícito en tus comentarios porque puede ser frágil y, como tú mismo dices, la gente generalmente no está acostumbrada a ver código como este.

Xor bit a bit! = Xor lógico (excepto para 0 y 1)

En primer lugar, si está operando con valores distintos de falsey true(o 0y 1, como enteros), el ^operador puede introducir un comportamiento no equivalente a un xor lógico. Por ejemplo:

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
}

Crédito al usuario @Patrick por expresar esto primero.

Orden de operaciones

En segundo lugar, |, &, y ^, como operadores de bits, no provoque un cortocircuito. Además, varios operadores bit a bit encadenados en una sola declaración, incluso con paréntesis explícitos, se pueden reordenar optimizando los compiladores, ya que las 3 operaciones son normalmente conmutativas. Esto es importante si importa el orden de las operaciones.

En otras palabras

bool result = true;
result = result && a() && b();
// will not call a() if result false, will not call b() if result or a() false

no siempre dará el mismo resultado (o 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

Esto es especialmente importante porque es posible que no controle los métodos a()y b(), o alguien más puede venir y cambiarlos más tarde sin comprender la dependencia, y causar un error desagradable (y, a menudo, solo para la versión de lanzamiento).

Patrick Johnmeyer
fuente
Realmente no es una comparación justa si lanzas a bool en un caso y no en el otro ... El lanzamiento de bool en ambos casos es lo que lo haría funcionar y por qué es frágil (porque tienes que recordarlo).
Greg Rogers
La explicación del cortocircuito es por qué esta debería ser absolutamente la respuesta aceptada; La respuesta de Patrick (err ... el otro Patrick, que se acaba de llamar Patrick) es completamente incorrecta al decir "Cuando el dominio de los números en el que opera es solo 1 y 0, entonces son exactamente iguales"
codetaku
13

Yo creo que

a != b

es lo que quieres

Mark Borgerding
fuente
1
Esto es cierto, +1. Pero no hay una versión de asignación de este operador ( !==por así decirlo), por lo que si está calculando el XOR de una secuencia de boolvalores, deberá escribir acc = acc!= condition(i);en un cuerpo de bucle. El compilador probablemente pueda manejar esto tan eficientemente como si!== existiera, pero algunos pueden encontrar que no se ve bien y prefieren la alternativa de agregar los valores booleanos como enteros y luego probar si la suma es impar.
Marc van Leeuwen
10

Las cejas levantadas deberían decirle lo suficiente como para dejar de hacerlo. No escribe el código para el compilador, primero lo escribe para sus compañeros programadores y luego para el compilador. Incluso si los compiladores funcionan, sorprender a otras personas no es lo que desea: los operadores bit a bit son para operaciones de bit, no para bools.
¿Supongo que también comes manzanas con tenedor? Funciona pero sorprende a la gente, así que es mejor no hacerlo.

kokos
fuente
3
Sí, esta debería ser la respuesta principal. Siga el principio de la mínima sorpresa. El código que hace cosas inusuales es más difícil de leer y comprender, y el código se lee con mucha más frecuencia de lo que se escribe. No uses trucos cursis como este.
Eloff
1
Estoy aquí porque mi colega usó el operador "y" bit a bit para bool. Y ahora paso mi tiempo tratando de entender si esto es correcto o no. Si no hiciera esto, ahora estaría haciendo algo más útil -_-. Si valora el tiempo de sus colegas, ¡POR FAVOR, no use operadores bit a bit para bool!
anton_rh
7

Desventajas de los operadores de nivel de bits.

Usted pregunta:

“¿Hay alguna razón para no usar los operadores de bits &, |y ^para los 'valores BOOL' en C ++? "

Sí, los operadores lógicos , que es la incorporada en operadores booleanos de alto nivel !, &&y ||, ofrecer las siguientes ventajas:

  • Garantizada conversión de argumentos a bool, es decir, a 0y 1valor ordinal.

  • Evaluación de cortocircuito garantizada donde la evaluación de expresión se detiene tan pronto como se conoce el resultado final.
    Esto se puede interpretar como una lógica de valor de árbol, con Verdadero , Falso e Indeterminado .

  • Equivalentes textuales legibles not, ande orincluso si no los uso yo mismo.
    Como lector de antimonio notas en un comentario también a los operadores bitlevel tienen fichas alternativas, a saber bitand, bitor,xor y compl, pero en mi opinión estos son menos legible que and, ory not.

En pocas palabras, cada una de estas ventajas de los operadores de alto nivel es una desventaja de los operadores de nivel de bits.

En particular, dado que los operadores bit a bit carecen de conversión de argumentos a 0/1, se obtiene, por ejemplo , 1 & 20, mientras que 1 && 2true. Además ^, bit a bit exclusivo o, puede comportarse mal de esta manera. Considerados como valores booleanos 1 y 2 son iguales, es decir true, pero considerados como patrones de bits, son diferentes.


Cómo expresar una lógica o una en C ++.

A continuación, proporcione algunos antecedentes para la pregunta,

"A veces me encuentro con situaciones en las que quiero que se cumpla exactamente una de las dos condiciones (XOR), por lo que simplemente lanzo el operador ^ a una expresión condicional".

Bueno, los operadores bit a bit tienen mayor precedencia que los operadores lógicos. Esto significa, en particular, que en una expresión mixta como

a && b ^ c

obtienes el resultado quizás inesperado a && (b ^ c) .

En su lugar, escribe solo

(a && b) != c

expresando de forma más concisa lo que quieres decir.

Para el argumento múltiple o / o no hay operador de C ++ que hace el trabajo. Por ejemplo, si se escribe a ^ b ^ cde lo que no es una expresión que dice “ya sea a, bo ces verdad”. En su lugar, dice, “un número impar de a, byc son verdaderos”, que podría ser 1 de ellos o los 3 ...

Para expresar el general o cuando a, by cson de tipo bool, simplemente escriba

(a + b + c) == 1

o, sin boolargumentos, conviértalos a bool:

(!!a + !!b + !!c) == 1


Utilizando &=para acumular resultados booleanos.

Usted elabora más,

"También necesito acumular valores booleanos a veces, &=y |=?puede ser bastante útil".

Bueno, esto corresponde a comprobar si se cumple respectivamente todas o alguna de las condiciones, y la ley de De Morgan te dice cómo pasar de una a otra. Es decir, solo necesitas uno de ellos. En principio, podría utilizarlo *=como&&= operador (porque, como descubrió el bueno de George Boole, el Y lógico se puede expresar fácilmente como una multiplicación), pero creo que eso dejaría perplejos y quizás engañaría a los mantenedores del código.

Considere también:

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;
}

Salida con Visual C ++ 11.0 y g ++ 4.7.1:

cierto
falso

La razón de la diferencia en los resultados es que el nivel de bits &=no proporciona una conversión a boolsu argumento del lado derecho.

Entonces, ¿cuál de estos resultados desea utilizar &=?

Si es el primero, trueentonces defina mejor un operador (por ejemplo, como arriba) o una función nombrada, o use una conversión explícita de la expresión del lado derecho, o escriba la actualización completa.

Saludos y hth. - Alf
fuente
Los operadores bit a bit también tienen equivalentes de texto
Antimony
@Antimony: yo no entendía que el comentario al principio, pero sí, las operaciones bitlevel tener fichas alternativas bitand, bitor, xory compl. Creo que por eso utilicé la calificación "legible". Por supuesto, la legibilidad de eg compl 42es subjetiva. ;-)
Saludos y hth. - Alf
Por lo que puedo decir, los operadores lógicos pueden estar sobrecargados con otros tipos de argumentos (aunque generalmente no lo están), por lo que el primer punto "conversión garantizada de argumentos a bool" es solo una consecuencia de la convención, no una garantía de que el lenguaje C ++ realmente da tú.
Marc van Leeuwen
@MarcvanLeeuwen: Posiblemente su subsistema visual no pudo notar “¡los operadores booleanos de alto nivel incorporados!, && y ||”. Tienes razón en que estos operadores pueden estar sobrecargados, y luego un problema principal es que la semántica cambia sutilmente, ya no la evaluación de cortocircuitos. Pero ese es un problema con las sobrecargas, no con los operadores integrados.
Saludos y hth. - Alf
Eso es correcto (lo extrañé). Pero entonces la declaración sigue siendo engañosa, porque solo está diciendo que esos operadores toman argumentos booleanos; si (y solo si) se seleccionan las versiones integradas de los operadores, entonces el compilador habrá insertado una conversión de los argumentos. De manera similar, el operador de suma de enteros sin signo incorporado (alto nivel) +garantiza la conversión de sus argumentos a unsigned, pero eso no impide unsigned int n=-1; n+=3.14;que se utilice realmente la operación de suma double(¿o sí float?). (Compare su &=ejemplo.)
Marc van Leeuwen
3

Al contrario de la respuesta de Patrick, C ++ no tiene ^^ operador para realizar un cortocircuito o exclusivo. Si lo piensa por un segundo, tener un ^^operador no tendría sentido de todos modos: con exclusivo o, el resultado siempre depende de ambos operandos. Sin embargo, la advertencia de Patrick sobre los booltipos no "booleanos" es igualmente válida cuando se compara 1 & 2con 1 && 2. Un ejemplo clásico de esto es la GetMessage()función de Windows , que devuelve un tri-estado BOOL: distinto de cero 0, o -1.

Utilizando & lugar de &&y en |lugar de ||no es un error tipográfico poco común, por lo que si lo está haciendo deliberadamente, merece un comentario que diga por qué.

bk1e
fuente
6
Un ^^operador aún sería útil independientemente de la consideración de cortocircuito. A) evaluaría los operandos en un contexto booleano yb) garantizaría devolver un 1 o 0.
Craig McQueen
@CraigMcQueen. Es simple definir inline bool XOR(bool a,bool b) { return a!=b; }y obtener lo que desea, excepto que es una función en lugar de un operador (infijo). O podría usar !=o sobrecargar directamente algún otro operador con este significado, pero luego, por supuesto, debe tener mucho cuidado de no terminar accidentalmente usando una sobrecarga no intencional con el mismo nombre. Y de paso ||y &&volver trueo false, no 1o 0.
Marc van Leeuwen
Y parece que el hecho de que !, ||y &&evalúen sus argumentos en un contexto booleano es solo porque estos operadores rara vez están sobrecargados para aceptar otros tipos; Por lo que puedo decir, el lenguaje permite tales sobrecargas y, por lo tanto, no garantiza la evaluación de argumentos en un contexto booleano.
Marc van Leeuwen
2

Patrick hizo buenos puntos y no los voy a repetir. Sin embargo, podría sugerir reducir las declaraciones 'if' a un inglés legible siempre que sea posible utilizando variables booleanas bien nombradas. Por ejemplo, y esto está usando operadores booleanos, pero también podría usar bit a bit y nombrar los bools de manera apropiada:

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

Puede pensar que usar un booleano parece innecesario, pero ayuda con dos cosas principales:

  • Su código es más fácil de entender porque el booleano intermedio para la condición 'si' hace que la intención de la condición sea más explícita.
  • Si está utilizando código inesperado o no estándar, como operadores bit a bit en valores booleanos, la gente puede ver mucho más fácilmente por qué ha hecho esto.

EDITAR: No dijiste explícitamente que querías los condicionales para las declaraciones 'if' (aunque esto parece lo más probable), esa era mi suposición. Pero mi sugerencia de un valor booleano intermedio sigue en pie.

genix
fuente
1

El uso de operaciones bit a bit para bool ayuda a ahorrar lógica de predicción de rama innecesaria por parte del procesador, resultante de una instrucción 'cmp' introducida por operaciones lógicas.

Reemplazar la lógica con operaciones bit a bit (donde todos los operandos son bool) genera un código más eficiente que ofrece el mismo resultado. Idealmente, la eficiencia debería superar todos los beneficios de cortocircuito que se pueden aprovechar en el pedido mediante operaciones lógicas.

Esto puede hacer que el código sea un poco ilegible, aunque el programador debería comentarlo con las razones por las que se hizo así.

Anoop Menon
fuente
0

IIRC, muchos compiladores de C ++ advertirán cuando intenten convertir el resultado de una operación bit a bit como bool. Tendría que usar un tipo de conversión para hacer feliz al compilador.

El uso de una operación bit a bit en una expresión if tendría la misma crítica, aunque quizás no por parte del compilador. Cualquier valor distinto de cero se considera verdadero, por lo que algo como "si (7 & 3)" será verdadero. Este comportamiento puede ser aceptable en Perl, pero C / C ++ son lenguajes muy explícitos. Creo que la ceja de Spock es debida diligencia. :) Agregaría "== 0" o "! = 0" para dejar perfectamente claro cuál era su objetivo.

Pero de todos modos, parece una preferencia personal. Ejecutaría el código a través de lint o una herramienta similar y vería si también cree que es una estrategia imprudente. Personalmente, parece un error de codificación.

Spoulson
fuente
¡Su publicación me motivó a probar esto y gcc no advirtió! Qué lástima, porque iba a usar esa advertencia para justificar que me dejara ejecutar & = para acumular resultados bool, confiando en que otros verían la advertencia si luego cambian mi prueba. Mi código fallaría incluso para enteros, ya que se evalúan como 0 / falso, ¡sin advertencias! Es hora de refactorizar ...
sabio