Negação Dupla no Código C ++

Eu acabei de entrar em um projeto com uma base de código muito grande.

Eu estou lidando principalmente com C ++ e muito do código que eles escrevem usa dupla negação para sua lógica booleana.

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

Eu sei que esses caras são programadores inteligentes, é óbvio que eles não estão fazendo isso por acidente.

Eu não sou experiente em C ++, meu único palpite sobre o porquê eles estão fazendo isso é que eles querem fazer absolutamente positivo que o valor sendo avaliado seja a representação booleana real. Então, eles negam isso, então negam isso novamente para recuperá-lo de volta ao seu valor booleano real.

Isso é correto, ou estou faltando alguma coisa?

É um truque para converter em bool.

Na verdade, é um idioma muito útil em alguns contextos. Tome essas macros (exemplo do kernel do Linux). Para o GCC, eles são implementados da seguinte maneira:

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

Por que eles têm que fazer isso? O __builtin_expect do GCC trata seus parâmetros como long e não bool , portanto, é necessário que haja alguma forma de conversão. Uma vez que eles não sabem o que é quando estão escrevendo essas macros, é mais geral simplesmente usar o !! idioma.

Eles provavelmente poderiam fazer a mesma coisa comparando com 0, mas na minha opinião, é realmente mais simples fazer a negação dupla, já que é o mais próximo de um casting para bool que C tem.

Este código pode ser usado em C ++ também … é uma coisa de menor denominador comum. Se possível, faça o que funciona em C e C ++.

Os codificadores acham que ele converterá o operando em bool, mas como os operandos de && já estão implicitamente convertidos em bool, é totalmente redundante.

Sim, é correto e não, você não está faltando alguma coisa. !! é uma conversão para bool. Veja esta questão para mais discussão.

É uma técnica para evitar a escrita (variável! = 0) – ou seja, para converter de qualquer tipo é para um bool.

Código IMO como este não tem lugar em sistemas que precisam ser mantidos – porque não é código imediatamente legível (daí a questão em primeiro lugar).

O código deve ser legível – caso contrário, você deixa um legado de dívida para o futuro – já que leva tempo para entender algo que é desnecessariamente confuso.

Ele segue um aviso do compilador. Tente isto:

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

A linha ‘bar’ gera um “valor de forçamento para bool ‘true’ ou ‘false’ (aviso de desempenho)” no MSVC ++, mas a linha “baz” passa despercebida.

É operador! sobrecarregado?
Se não, provavelmente eles estão fazendo isso para converter a variável em um bool sem produzir um aviso. Esta definitivamente não é uma maneira padrão de fazer as coisas.

Os desenvolvedores legados de C não possuíam um tipo booleano, portanto, com frequência #define TRUE 1 e #define FALSE 0 e usavam tipos de dados numéricos arbitrários para comparações booleanas. Agora que temos bool , muitos compiladores emitem avisos quando determinados tipos de atribuições e comparações são feitas usando uma mistura de tipos numéricos e tipos booleanos. Esses dois usos acabarão colidindo ao trabalhar com o código legado.

Para contornar esse problema, alguns desenvolvedores usam a seguinte identidade booleana !num_value retorna bool true se num_value == 0 ; false caso contrário. !!num_value retorna bool false se num_value == 0 ; true caso contrário. A negação única é suficiente para converter num_value em bool ; no entanto, a negação dupla é necessária para restaurar o sentido original da expressão booleana.

Esse padrão é conhecido como idioma , isto é, algo comumente usado por pessoas familiarizadas com a linguagem. Portanto, não vejo isso como um static_cast(num_value) , tanto quanto eu faria com static_cast(num_value) . O casting pode muito bem dar os resultados corretos, mas alguns compiladores então emitem um aviso de desempenho, então você ainda tem que resolver isso.

A outra maneira de resolver isso é dizer (num_value != FALSE) . Eu estou bem com isso também, mas apesar de tudo, !!num_value é muito menos detalhado, pode ser mais claro, e não é confuso na segunda vez que você o vê.

Como Marcin mencionou, pode muito bem se a sobrecarga do operador estiver em jogo. Caso contrário, em C / C ++, não importa, exceto se você estiver fazendo uma das seguintes ações:

  • comparação direta para true (ou em C algo como uma macro TRUE ), que é quase sempre uma má ideia. Por exemplo:

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

  • você simplesmente quer algo convertido em um valor 0/1 estrito. Em C ++, uma atribuição a um bool fará isso implicitamente (para aquelas coisas que são implicitamente conversíveis em bool ). Em C ou se você está lidando com uma variável não-bool, este é um idioma que eu já vi, mas eu prefiro a (some_variable != 0) eu mesmo.

Eu acho que no contexto de uma expressão booleana maior, ela simplesmente desordena as coisas.

Se variável é do tipo de object, pode ter um! operador definido, mas não cast to bool (ou pior um casting implícito para int com semântica diferente. Chamando o operador! duas vezes resulta em um convertido em bool que funciona mesmo em casos estranhos.

!! foi usado para lidar com C ++ original que não tinha um tipo booleano (como nem C).


Exemplo de problema:

Dentro de if(condition) , a condition precisa avaliar algum tipo como double, int, void* , etc., mas não como bool , pois ainda não existe.

Digamos que uma class existia int256 (um inteiro de 256 bits) e todas as conversões / conversões de inteiros estavam sobrecarregadas.

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

Para testar se x era “true” ou diferente de zero, if (x) converteria x em algum inteiro e, em seguida, avaliaria se esse int era diferente de zero. Uma sobrecarga típica de (int) x retornaria apenas os LSbits de x . if (x) estava, então, apenas testando os LSbits de x .

Mas o C ++ tem o ! operador. Um sobrecarregado !x normalmente avaliaria todos os bits de x . Então, para voltar à lógica não invertida, if (!!x) é usado.

Ref As versões mais antigas do C ++ usam o operador `int` de uma class ao avaliar a condição em uma instrução` if () `?

Está correto, mas, em C, sem sentido aqui – ‘if’ e ‘&&’ tratariam a expressão da mesma maneira sem o ‘!!’.

A razão para fazer isso em C ++, suponho, é que ‘&&’ poderia estar sobrecarregado. Mas então, então poderia ‘!’, Então realmente não garante que você obtenha um bool, sem olhar o código para os tipos de variable e api.call . Talvez alguém com mais experiência em C ++ possa explicar; talvez seja uma medida de defesa em profundidade, não uma garantia.

Talvez os programadores estivessem pensando em algo assim …

!! myAnswer é booleano. No contexto, ele deve se tornar booleano, mas eu adoro bater coisas para ter certeza, porque antigamente havia um bug misterioso que me mordeu, e bang bang, eu o matei.

Este pode ser um exemplo do truque do double-bang , veja The Safe Bool Idiom para mais detalhes. Aqui eu resumir a primeira página do artigo.

Em C ++, existem várias maneiras de fornecer testes booleanos para classs.

Uma maneira óbvia é o operator bool conversão operator bool operador.

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

Nós podemos testar a class,

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

No entanto, opereator bool é considerado inseguro porque permite operações sem sentido, como test < < 1; ou int i=test .

Usando o operator! é mais seguro porque evitamos problemas implícitos de conversão ou sobrecarga.

A implementação é trivial,

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

As duas formas idiomáticas para testar o object Testable são

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

A primeira versão if (!!test) é o que algumas pessoas chamam de truque do duplo estrondo .