2008-10-29 17 views
100

Acabo de entrar en un proyecto con una base de código bastante grande.Double Negation en C++ código

Principalmente trato 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 en C++ experimentado, mi única conjetura de por qué lo hacen 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 esto correcto o me falta algo?

+0

Este tema se ha debatido [aquí] (http://stackoverflow.com/questions/206106/is-a-safe-way-to-convert-to-bool-in-c#206122). – Dima

+4

marque aquí, ya se lo preguntó, [¡¡¡¡¡¡¡¡¡¡¡Es !! una forma segura de convertir a bool en C++?] (http://stackoverflow.com/questions/206106/is-a-safe-way-to-convert-to-bool-in-c) –

+0

Posible duplicado de [Is !! una forma segura de convertir a bool en C++?] (https://stackoverflow.com/questions/206106/is-a-safe-way-to-convert-to-bool-in-c) – EdChum

Respuesta

104

Es un truco para convertir a bool.

+3

Exactamente, es un idioma común en la mayoría de los idiomas donde! es el operador de negación con un molde implícito a boolean – Gareth

+13

Creo que lanzarlo explícitamente con (bool) sería más claro, ¿por qué usar esto complicado !!, porque es de menos tipeo? –

+21

Sin embargo, no tiene sentido en C++ o C moderno, o donde el resultado solo se usa en una expresión booleana (como en la pregunta). Fue útil cuando no teníamos el tipo 'bool', para ayudar a evitar el almacenamiento de valores distintos de' 1' y '0' en variables booleanas. –

5

Es el operador! ¿sobrecargado?
Si no, probablemente lo estén haciendo para convertir la variable a bool sin producir una advertencia. Esta definitivamente no es una forma estándar de hacer las cosas.

10

Sí, es correcto y no te falta algo. !! es una conversión a bool. Vea this question para más discusión.

9

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

IMO Este tipo de código no tiene cabida en los sistemas que necesitan mantenimiento, ya que no se trata de un código inmediatamente legible (de ahí la pregunta, en primer lugar).

El código debe ser legible; de ​​lo contrario, deje un legado de deuda de tiempo para el futuro, ya que toma tiempo comprender algo innecesariamente complejo.

+7

No es un truco, es simple encasillamiento. – moo

+6

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

+5

@ orlandu63: el encasillado simple es 'bool (expr)': hace lo correcto y todos entienden el intento a primera vista. '!! (expr)' es una doble negación, que accidentalmente se convierte en bool ... esto no es simple. –

47

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

+13

Visual C++ ofrece una interpretación de wandering en algunos casos sin este truco. –

+2

Visual C++ 2015 continúa dando advertencia de rendimiento. – BSalita

+0

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

0

Es correcto pero, en C, inútil 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 '!', Así que no realmente le garantiza obtener un bool, sin mirar el código para los tipos de variable y api.call. Tal vez alguien con más experiencia en C++ podría explicar; quizás sea una medida de defensa en profundidad, no una garantía.

+0

El compilador trataría los valores de la misma forma si solo se usa como un operando para un 'if' o' && ', pero usar' !! 'puede ayudar en algunos compiladores si' if (!! (number & mask)) 'se reemplaza con' bit triggered = !! (number & mask); if (activado) '; en algunos compiladores integrados con tipos de bits, asignando p. 256 a un tipo de bit rendirá cero. Sin el '!!', la transformación aparentemente segura (copiar la condición 'if' a una variable y luego bifurcar) no será segura. – supercat

1

Como se mencionó Marcin, podría ser muy importante 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 a true (o en C algo así como un TRUE macro), que es casi siempre una mala idea.Por ejemplo:

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

  • simplemente desea algo convertida a un estricto valor 0/1. En C++ una asignación a bool hará esto implícitamente (para aquellos elementos que son implícitamente convertibles a bool). En C o si se trata de una variable no bool, esta es una expresión que he visto, pero prefiero la variedad (some_variable != 0).

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

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 alguna vez hubo un error misterioso que me mordió, y bang bang, lo maté.

64

En realidad, es una expresión muy útil en algunos contextos. Tome estas macros (ejemplo del kernel de Linux). Para GCC, que se implementen las siguientes:

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

¿Por qué tienen que hacer esto? El __builtin_expect de GCC trata sus parámetros como long y no como bool, por lo que debe haber alguna forma de conversión. Como no saben qué es cond cuando escriben esas macros, es más general simplemente usar la expresión !!.

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 parecido a un elenco a bool que tiene C.

Este código también se puede usar en C++ ... es lo más común en el denominador. Si es posible, haz lo que funciona tanto en C como en C++.

+5

gracias a christ la respuesta correcta –

+0

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 tiene solo un bit alto, 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, negando nuevamente da como resultado un booleano que representa la verdad original. –

+0

Como SO no me permitirá actualizar mi comentario, agregaré una solución a mi error. Negar un valor integral resulta en una conversión booleana (falso si no es cero, de lo contrario es verdadero). –

8

Se muestra de forma lateral un compilador de advertencia. Prueba esto:

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

La línea de 'bar' genera un "valor obligando a bool (advertencia de rendimiento) 'verdadero' o 'falso'" en MSVC++, pero la línea 'baz' se cuela a través de multa.

+1

Más comúnmente encontrado en la API de Windows que no conoce el tipo 'bool' - todo está codificado como' 0' o '1' en un' int'. –

1

Si variable es del tipo de objeto, podría 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.

1

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


Ejemplo Problema:.

Interior if(condition), los condition tiene que evaluar a algún tipo como double, int, void*, etc., pero no bool como lo hace no existe todavía.

Supongamos que existe una clase int256 (un entero de 256 bits) y todas las conversiones/moldes enteros están sobrecargadas.

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

Para probar si x era "verdadero" o no es cero, if (x) sería convertir x para algún entero y entonces evaluar si que int era distinto de cero. Una sobrecarga típica de (int) x devolvería solo los LSbits de x. if (x) estaba probando los LSbits de x.

Pero C++ tiene el operador !. Una !x sobrecargada normalmente evaluaría todos los bits de x. Por lo tanto, para volver a la lógica no invertida, se usa if (!!x).

Ref Did older versions of C++ use the `int` operator of a class when evaluating the condition in an `if()` statement?

2

desarrolladores Legado C no tenían ningún tipo booleano, lo que a menudo #define TRUE 1 y #define FALSE 0 y luego utilizan tipos de datos numéricos arbitrarios para las comparaciones booleanas. Ahora que tenemos bool, muchos compiladores emitirán advertencias cuando se realicen ciertos tipos de asignaciones y comparaciones utilizando una combinación de tipos numéricos y booleanos. Estos dos usos eventualmente colisionarán cuando se trabaja con código heredado. Para resolver este problema, algunos desarrolladores usan la siguiente identidad booleana: !num_value devuelve bool true si num_value == 0; false de lo contrario. !!num_value devuelve bool false si num_value == 0; true en caso contrario. La única negación es suficiente para convertir num_value en 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 idioma, es decir, algo comúnmente utilizado por personas familiarizadas con el idioma. Por lo tanto, no lo veo como un anti-patrón, tanto como lo haría static_cast<bool>(num_value). El elenco podría dar los resultados correctos, pero algunos compiladores emiten una advertencia de rendimiento, por lo que todavía tiene que abordar eso.

La otra forma de abordar esto es decir, (num_value != FALSE). Estoy de acuerdo con eso también, pero en general, !!num_value es mucho menos detallado, puede ser más claro y no confunde la segunda vez que lo ves.

0

Esto puede ser un ejemplo del truco de 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 maneras de proporcionar pruebas booleanas para las clases.

Una forma obvia es operator bool operador 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 bool se considera insegura, ya que permite operaciones sin sentido como test << 1; o int i=test.

El uso de operator! es más seguro porque evitamos problemas de conversión o sobrecarga.

La implementación es trivial,

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

Las dos formas idiomáticas para probar Testable objeto 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 algunos llaman el truco doble-bang .

+0

Desde C++ 11, se puede usar '' operator explícitamente bool'' para evitar la conversión implícita a otros tipos integrales. –