Negação dupla em C ++

124

Acabei de entrar em um projeto com uma enorme base de código.

Estou lidando principalmente com C ++ e grande parte do código que eles escrevem usa negação dupla 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 não estão fazendo isso por acidente.

Não sou especialista em C ++ experiente, meu único palpite sobre o motivo pelo qual eles estão fazendo isso é que eles querem ter certeza absoluta de que o valor que está sendo avaliado é a representação booleana real. Então, eles negam e depois negam isso novamente para retornar ao seu valor booleano real.

Isso está correto ou estou faltando alguma coisa?

Brian Gianforcaro
fonte
4
confira aqui, já perguntei, é !! uma maneira segura de converter para bool em C ++?
Ozgür
Este tópico foi discutido aqui .
Dima

Respostas:

121

É um truque para converter em bool.

Don Neufeld
fonte
19
Eu acho que lançá-lo explicitamente com (bool) seria mais claro, por que usar esse truque !!, porque é de menos digitação?
Baiyan Huang 29/03/10
27
No entanto, é inútil em C ++ ou C moderno, ou onde o resultado é usado apenas em uma expressão booleana (como na pergunta). Foi útil quando não tínhamos booltipo, para ajudar a evitar o armazenamento de valores diferentes de 1e 0em variáveis ​​booleanas.
Mike Seymour
6
@lzprgmr: conversão explícita causa um "aviso de desempenho" no MSVC. Usando !!ou !=0resolvendo o problema, e dentre os dois, encontro o limpador mais antigo (já que ele funcionará em uma quantidade maior de tipos). Também concordo que não há razão para usar o código em questão.
Yakov Galka
6
@ Noldorin, acho que melhora a legibilidade - se você sabe o que significa, é simples, puro e lógico.
GTC
19
Melhora? Caramba, me dê um pouco do que você está fumando.
Noldorin
73

Na verdade, é um idioma muito útil em alguns contextos. Pegue 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 GCC __builtin_expecttrata seus parâmetros como longnão e bool, portanto, precisa haver alguma forma de conversão. Como eles não sabem o que condé quando estão escrevendo essas macros, é mais geral simplesmente usar o !!idioma.

Provavelmente, eles poderiam fazer a mesma coisa comparando com 0, mas, na minha opinião, é mais simples fazer a negação dupla, já que é o mais próximo de um cast-to-bool que C possui.

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

Tom Barta
fonte
Eu acho que isso faz muito sentido quando você pensa sobre isso. Não li todas as respostas, mas parece que o processo de conversão não está especificado. Se tivermos um valor com 2 bits de altura e um valor que tenha apenas um bit de altura, teremos um valor diferente de zero. Negar um valor diferente de zero resulta em uma conversão booleana (false se for zero, true caso contrário). Negar novamente resulta em um booleano que representa a verdade original.
Joey Carson
Como o SO não permitirá que eu atualize meu comentário, adicionarei uma correção ao meu erro. Negar um valor integral resulta em uma conversão booleana (false se for diferente de zero, true caso contrário).
Joey Carson
51

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

fizzer
fonte
14
O Visual C ++ fornece uma redução de desempenho em alguns casos sem esse truque.
Kirill V. Lyadvinsky
1
Suponho que seria melhor apenas desativar o aviso do que contornar avisos inúteis no código.
Ruslan
Talvez eles não percebam isso. No entanto, isso faz todo o sentido no contexto de uma macro, onde você pode trabalhar com tipos de dados integrais dos quais não conhece. Considere objetos com o operador de parênteses sobrecarregado para retornar um valor integral representando um campo de bit.
Joey Carson
12

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

Um código IMO como esse não tem lugar em sistemas que precisam ser mantidos - porque não é um código imediatamente legível (daí a pergunta em primeiro lugar).

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

Richard Harrison
fonte
8
Minha definição de truque é algo que nem todos podem entender na primeira leitura. Algo que precisa descobrir é um truque. Também horrível porque o! operador pode ser sobrecarregado ...
Richard Harrison
6
@ orlandu63: a conversão de texto simples é bool(expr): faz a coisa certa e todos entendem a intenção à primeira vista. !!(expr)é uma dupla negação, que acidentalmente se converte em bool ... isso não é simples.
Adrien Plisson
12

Sim, está correto e não, você não está perdendo algo. !!é uma conversão para bool. Veja esta pergunta para mais discussão.

jwfearn
fonte
9

Desvia um aviso do compilador. Tente o seguinte:

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

A linha 'bar' gera um "valor forçado para bool 'true' ou 'false' (aviso de desempenho)" no MSVC ++, mas a linha 'baz' foge bem.

RobH
fonte
1
Mais comumente encontrado na própria API do Windows que não sabe sobre o booltipo - tudo é codificado como 0ou 1em um int.
Mark Ransom
4

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

Marcin
fonte
4

Desenvolvedores legado C teve nenhum tipo booleano, para que eles muitas vezes #define TRUE 1e #define FALSE 0em seguida utilizado tipos de dados numéricos arbitrários para comparações booleanas. Agora que temos bool, muitos compiladores emitem avisos quando certos 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 código legado.

Para contornar esse problema, alguns desenvolvedores usam a seguinte identidade booleana: !num_valueretorna bool trueif num_value == 0; falsede outra forma. !!num_valueretorna bool falsese num_value == 0; truede outra forma. O único negação é suficiente para converter num_valuea bool; no entanto, a dupla negação é 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 o idioma. Portanto, não o vejo como um antipadrão, por mais que eu o visse static_cast<bool>(num_value). O elenco pode muito bem dar os resultados corretos, mas alguns compiladores emitem um aviso de desempenho, então você ainda precisa resolver isso.

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

KarlU
fonte
2

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


Problema de exemplo:

No interior if(condition), as conditionnecessidades devem ser avaliadas para algum tipo double, int, void*, etc., mas não boolcomo ainda não existe.

Digamos que exista uma classe int256(um número inteiro de 256 bits) e todas as conversões / conversões inteiras foram sobrecarregadas.

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

Para testar se xera "verdadeiro" ou diferente de zero, if (x)seria convertido xem algum número inteiro e, em seguida, avaliaria se intera diferente de zero. Uma sobrecarga típica de (int) xretornaria apenas os LSbits de x. if (x)estava testando apenas os LSbits de x.

Mas o C ++ tem o !operador. Uma sobrecarga !xnormalmente 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 ++ usaram o operador `int` de uma classe ao avaliar a condição em uma instrução` if () `?

chux - Restabelecer Monica
fonte
1

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

  • comparação direta com true(ou em C algo como uma TRUEmacro), que quase sempre é uma má idéia. Por exemplo:

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

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

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

Michael Burr
fonte
1

Se a variável for do tipo de objeto, pode ter um! operador definido, mas sem conversão para bool (ou pior, uma conversão implícita para int com diferentes semânticas. Chamar o operador! duas vezes resulta em uma conversão em bool que funciona mesmo em casos estranhos.

Joshua
fonte
0

Está correto, mas em C não faz sentido aqui - 'if' e '&&' tratariam a expressão da mesma maneira sem o '!!'.

O motivo para fazer isso em C ++, suponho, é que '&&' pode estar sobrecarregado. Mas então, por isso poderia '!', Para que ele não realmente garantir-lhe obter um bool, sem olhar para o código para os tipos de variablee 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.

Darius Bacon
fonte
O compilador trataria os valores da mesma maneira, se for usado apenas como um operando para um ifou &&, mas o uso !!pode ajudar em alguns compiladores se if (!!(number & mask))for substituído por bit triggered = !!(number & mask); if (triggered); em alguns compiladores incorporados com tipos de bits, atribuir, por exemplo, 256 a um tipo de bit, produzirá zero. Sem o !!, a transformação aparentemente segura (copiar a ifcondição para uma variável e depois ramificar) não será segura.
supercat
0

Talvez os programadores estivessem pensando algo assim ...

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

dongilmore
fonte
0

Este pode ser um exemplo do truque de golpe duplo , consulte The Safe Bool Idiom para obter mais detalhes. Aqui eu resumo a primeira página do artigo.

No C ++, existem várias maneiras de fornecer testes booleanos para classes.

Uma maneira óbvia é o operator booloperador de conversão.

// 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 testar a classe,

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.

O uso operator!é mais seguro, pois evitamos problemas implícitos de conversão ou sobrecarga.

A implementação é trivial,

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

As duas maneiras idiomáticas de testar o Testableobjeto 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 golpe duplo .

kgf3JfUtW
fonte
1
Desde o C ++ 11, pode-se usar explicit operator boolpara impedir a conversão implícita em outros tipos integrais.
Arne Vogel