Doble negación en C ++


124

Acabo de llegar a un proyecto con una base de código bastante grande.

Principalmente estoy tratando con C ++ y gran parte del código que escriben usa doble negación para su lógica booleana.

 if (!!variable && (!!api.lookup("some-string"))) {
       do_some_stuff();
 }                                   

Sé que estos tipos son programadores inteligentes, es obvio que no están haciendo esto por accidente.

No soy un experto experimentado en C ++, mi única suposición de por qué están haciendo esto es que quieren hacer absolutamente positivo que el valor que se evalúa es la representación booleana real. Entonces lo niegan, luego lo niegan nuevamente para volver a su valor booleano real.

¿Es correcto o me falta algo?



Este tema ha sido discutido aquí .
Dima

Respuestas:


121

Es un truco convertir a bool.


19
Creo que lanzarlo explícitamente con (bool) sería más claro, ¿por qué usar este truco? ¡Porque es menos tipeado?
Baiyan Huang

27
Sin embargo, no tiene sentido en C ++ o C moderna, o donde el resultado solo se usa en una expresión booleana (como en la pregunta). Fue útil cuando no teníamos booltipo, para ayudar a evitar almacenar valores distintos de 1y 0en variables booleanas.
Mike Seymour

66
@lzprgmr: la conversión explícita provoca una "advertencia de rendimiento" en MSVC. Usando !!o !=0resuelve el problema, y ​​entre los dos encuentro el ex limpiador (ya que funcionará en una mayor cantidad de tipos). También estoy de acuerdo en que no hay ninguna razón para usar tampoco en el código en cuestión.
Yakov Galka

66
@Noldorin, creo que mejora la legibilidad: si sabes lo que significa, es simple, ordenado y lógico.
jwg

19
Mejora? Maldita sea ... dame algo de lo que estés fumando.
Noldorin

73

En realidad, es un idioma muy útil en algunos contextos. Tome estas macros (ejemplo del kernel de Linux). Para GCC, se implementan de la siguiente manera:

#define likely(cond)   (__builtin_expect(!!(cond), 1))
#define unlikely(cond) (__builtin_expect(!!(cond), 0))

¿Por qué tienen que hacer esto? GCC __builtin_expecttrata sus parámetros como longy no bool, por lo que debe haber alguna forma de conversión. Como no saben qué condes cuando escriben esas macros, lo más general es simplemente usar el !!idioma.

Probablemente podrían hacer lo mismo comparando con 0, pero en mi opinión, en realidad es más sencillo hacer la doble negación, ya que es lo más cercano a un cast-to-bool que tiene C.

Este código también se puede usar en C ++ ... es una cosa de mínimo común denominador. Si es posible, haga lo que funciona tanto en C como en C ++.


Creo que esto tiene mucho sentido cuando lo piensas bien. No he leído todas las respuestas, pero parece que el proceso de conversión no está especificado. Si tenemos un valor con 2 bits de altura y con un valor que solo tiene un bit de altura, tendremos un valor distinto de cero. Negar un valor distinto de cero da como resultado una conversión booleana (falso si es cero, de lo contrario es verdadero). Luego, negar nuevamente da como resultado un valor booleano que representa la verdad original.
Joey Carson

Dado que SO no me permitirá actualizar mi comentario, agregaré una solución a mi error. Negar un valor integral da como resultado una conversión booleana (falso si no es cero, verdadero de lo contrario).
Joey Carson

51

Los codificadores piensan que convertirá el operando a bool, pero debido a que los operandos de && ya están implícitamente convertidos a bool, es completamente redundante.


14
Visual C ++ ofrece un rendimiento errante en algunos casos sin este truco.
Kirill V. Lyadvinsky

1
Supongo que sería mejor deshabilitar la advertencia que evitar advertencias inútiles en el código.
Ruslan

Quizás no se den cuenta. Sin embargo, esto tiene mucho sentido en el contexto de una macro, donde podría estar trabajando con tipos de datos integrales que no conoce. Considere los objetos con el operador de paréntesis sobrecargado para devolver el valor integral que representa un campo de bits.
Joey Carson

12

Es una técnica para evitar escribir (variable! = 0), es decir, convertir de cualquier tipo que sea a un bool.

El código IMO como este no tiene cabida en los sistemas que deben mantenerse, porque no es un código legible de inmediato (de ahí la pregunta en primer lugar).

El código debe ser legible; de ​​lo contrario, dejará un legado de deuda temporal para el futuro, ya que lleva tiempo comprender algo que es innecesariamente complicado.


8
Mi definición de un truco es algo que no todos pueden entender en la primera lectura. Algo que necesita resolver es un truco. También horrible porque el! operador podría estar sobrecargado ...
Richard Harrison

66
@ orlandu63: el encuadre simple es bool(expr): hace lo correcto y todos entienden la intención a primera vista. !!(expr)es una doble negación, que accidentalmente se convierte en bool ... esto no es simple.
Adrien Plisson

12

Sí, es correcto y no, no te estás perdiendo algo. !!Es una conversión a bool. Vea esta pregunta para más discusión.


9

Evita una advertencia del compilador. Prueba esto:

int _tmain(int argc, _TCHAR* argv[])
{
    int foo = 5;
    bool bar = foo;
    bool baz = !!foo;
    return 0;
}

La línea 'bar' genera un "valor de forzamiento para bool 'verdadero' o 'falso' (advertencia de rendimiento)" en MSVC ++, pero la línea 'baz' se cuela bien.


1
Se encuentra más comúnmente en la API de Windows en sí, que no conoce el booltipo: todo está codificado como 0o 1en un int.
Mark Ransom

4

Es operador! ¿sobrecargado?
Si no, probablemente estén haciendo esto para convertir la variable en un bool sin generar una advertencia. Definitivamente, esta no es una forma estándar de hacer las cosas.


4

Desarrolladores legado C no tenían ningún tipo booleano, lo que a menudo #define TRUE 1y #define FALSE 0a continuación, utilizan tipos de datos numéricos arbitrarios para las comparaciones booleanas. Ahora que lo tenemos bool, muchos compiladores emitirán advertencias cuando se realicen ciertos tipos de asignaciones y comparaciones utilizando una mezcla de tipos numéricos y tipos booleanos. Estos dos usos eventualmente colisionarán cuando se trabaje con código heredado.

Para solucionar este problema, algunos desarrolladores usan la siguiente identidad booleana: !num_valuedevuelve bool trueif num_value == 0; falsede otra manera. !!num_valuedevuelve bool falsesi num_value == 0; truede otra manera. La única negación es suficiente para convertir num_valuea bool; sin embargo, la doble negación es necesaria para restaurar el sentido original de la expresión booleana.

Este patrón se conoce como modismo , es decir, algo comúnmente utilizado por personas familiarizadas con el idioma. Por lo tanto, no lo veo como un antipatrón, tanto como lo haría static_cast<bool>(num_value). El elenco podría muy bien dar los resultados correctos, pero algunos compiladores luego emiten una advertencia de rendimiento, por lo que aún debe abordar eso.

La otra manera de abordar esto es decir, (num_value != FALSE). También estoy de acuerdo con eso, pero en general, !!num_valuees mucho menos detallado, puede ser más claro y no es confuso la segunda vez que lo ve.


2

!! se utilizó para hacer frente a C ++ original que no tenía un tipo booleano (como tampoco lo hizo C).


Problema de ejemplo:

En if(condition)el interior , la conditionnecesidad de evaluar a algún tipo como double, int, void*, etc., pero no boolcomo todavía no existe.

Digamos que existía una clase int256(un entero de 256 bits) y todas las conversiones / conversiones de enteros estaban sobrecargadas.

int256 x = foo();
if (x) ...

Para probar si xera "verdadero" o distinto de cero, if (x)convertiría xa algún número entero y luego evaluaría si intera distinto de cero. Una sobrecarga típica de (int) xdevolvería solo los LSbits de x. if (x)entonces solo estaba probando los LSbits de x.

Pero C ++ tiene el !operador. Una sobrecarga !xtípicamente evaluaría todos los bits de x. Entonces, para volver a la lógica no invertida if (!!x)se utiliza.

Ref. ¿Las versiones anteriores de C ++ utilizaron el operador `int` de una clase al evaluar la condición en una instrucción` if () `?


1

Como mencionó Marcin , bien podría importar si la sobrecarga del operador está en juego. De lo contrario, en C / C ++ no importa, excepto si está haciendo una de las siguientes cosas:

  • comparación directa con true(o en C algo así como una TRUEmacro), que casi siempre es una mala idea. Por ejemplo:

    if (api.lookup("some-string") == true) {...}

  • simplemente desea que algo se convierta en un valor estricto de 0/1. En C ++, una asignación a una boolvoluntad hará esto implícitamente (para aquellas cosas que son implícitamente convertibles a bool). En C o si se trata de una variable no bool, este es un idioma que he visto, pero prefiero la (some_variable != 0)variedad yo mismo.

Creo que en el contexto de una expresión booleana más grande, simplemente llena las cosas.


1

Si la variable es de tipo de objeto, puede tener un! operador definido pero sin conversión a bool (o peor, una conversión implícita a int con semántica diferente. Llamar al operador! dos veces resulta en una conversión a bool que funciona incluso en casos extraños.


0

Es correcto pero, en C, no tiene sentido aquí: 'if' y '&&' tratarían la expresión de la misma manera sin el '!!'.

La razón para hacer esto en C ++, supongo, es que '&&' podría estar sobrecargado. Pero entonces, también podría '!', Por lo que no garantiza realmente que obtenga un bool, sin mirar el código para los tipos de variabley api.call. Quizás alguien con más experiencia en C ++ podría explicarlo; tal vez sea una medida de defensa en profundidad, no una garantía.


El compilador trataría los valores de la misma manera si se usa solo como un operando para un ifo &&, pero el uso !!puede ayudar en algunos compiladores si if (!!(number & mask))se reemplaza con bit triggered = !!(number & mask); if (triggered); en algunos compiladores integrados con tipos de bits, asignar, por ejemplo, 256 a un tipo de bits producirá cero. Sin la !!transformación aparentemente segura (copiar la ifcondición a una variable y luego ramificar) no será segura.
supercat

0

Tal vez los programadores estaban pensando algo como esto ...

!! myAnswer es booleano. En contexto, debería volverse booleano, pero me encanta golpear las cosas para asegurarme, porque había una vez un misterioso error que me mordió, y lo golpeé, lo maté.


0

Este puede ser un ejemplo del truco de la doble explosión , vea The Safe Bool Idiom para más detalles. Aquí resumo la primera página del artículo.

En C ++ hay varias formas de proporcionar pruebas booleanas para las clases.

Una forma obvia es el operator booloperador de conversión.

// operator bool version
  class Testable {
    bool ok_;
  public:
    explicit Testable(bool b=true):ok_(b) {}

    operator bool() const { // use bool conversion operator
      return ok_;
    }
  };

Podemos probar la clase,

Testable test;
  if (test) 
    std::cout << "Yes, test is working!\n";
  else 
    std::cout << "No, test is not working!\n";

Sin embargo, opereator boolse considera inseguro porque permite operaciones sin sentido como test << 1;o int i=test.

Usar operator!es más seguro porque evitamos problemas de conversión implícita o sobrecarga.

La implementación es trivial,

bool operator!() const { // use operator!
    return !ok_;
  }

Las dos formas idiomáticas de probar Testableobjetos son

  Testable test;
  if (!!test) 
    std::cout << "Yes, test is working!\n";
  if (!test2) {
    std::cout << "No, test2 is not working!\n";

La primera versión if (!!test)es lo que algunas personas llaman el truco del doble golpe .


1
Desde C ++ 11, se puede usar explicit operator boolpara evitar la conversión implícita a otros tipos integrales.
Arne Vogel
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.