web-dev-qa-db-fra.com

Double négation en code C++

Je viens d'arriver sur un projet avec une base de code assez énorme. 

Je traite principalement avec C++ et une grande partie du code qu'ils écrivent utilise la double négation pour leur logique booléenne. 

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

Je sais que ces gars sont des programmeurs intelligents, il est évident qu'ils ne le font pas par accident. 

Je ne suis pas un expert chevronné en C++. Je ne peux que comprendre pourquoi ils le font, mais ils veulent absolument s'assurer que la valeur évaluée est la représentation booléenne. Alors ils le nient, puis le nient à nouveau pour le ramener à sa valeur booléenne réelle. 

Est-ce correct ou est-ce que je manque quelque chose?

108
Brian Gianforcaro

C'est une astuce pour convertir en bool.

109
Don Neufeld

C'est en fait un idiome très utile dans certains contextes. Prenez ces macros (exemple du noyau Linux). Pour GCC, ils sont implémentés comme suit:

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

Pourquoi doivent-ils faire cela? Le __builtin_expect de GCC traite ses paramètres comme étant long et non bool; il doit donc exister une forme de conversion. Comme ils ne savent pas ce que cond est quand ils écrivent ces macros, il est très courant d'utiliser simplement l'idiome !!.

Ils pourraient probablement faire la même chose en comparant avec 0, mais à mon avis, il est en fait plus simple de faire la double négation, car c’est le cas le plus proche d’un casting.

Ce code peut également être utilisé en C++ ... c'est une chose qui a le plus petit dénominateur commun. Si possible, faites ce qui fonctionne à la fois en C et en C++.

69
Tom Barta

Les codeurs pensent que l'opérande sera converti en bool, mais comme les opérandes de && sont déjà implicitement convertis en bool, ils sont totalement redondants.

48
fizzer

Oui c'est correct et non vous ne manquez pas quelque chose. !! est une conversion en bool. Voir cette question pour plus de discussion.

11
jwfearn

C’est une technique pour éviter d’écrire (variable! = 0) - c’est-à-dire convertir de n'importe quel type en bool.

Le code IMO comme celui-ci n'a pas sa place dans les systèmes qui doivent être conservés - car il ne s'agit pas d'un code immédiatement lisible (d'où la question à la base). 

Le code doit être lisible - sinon vous laisserez un héritage de temps pour l'avenir - car il faut du temps pour comprendre quelque chose qui est inutilement compliqué.

10
Richard Harrison

Il met latéralement un avertissement au compilateur. Essaye ça:

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

La ligne "barre" génère une "valeur forçant à booléer" vrai "ou" faux "(avertissement de performance)" sous MSVC++, mais la ligne "baz" se faufile sans problème.

8
RobH

Est opérateur! surchargé?
Sinon, ils le font probablement pour convertir la variable en bool sans produire d'avertissement. Ce n'est certainement pas une façon standard de faire les choses.

5
Marcin

Les développeurs C hérités n'avaient pas de type booléen, ils ont donc souvent #define TRUE 1 et #define FALSE 0, puis ont utilisé des types de données numériques arbitraires pour les comparaisons booléennes. Maintenant que nous avons bool, de nombreux compilateurs émettront des avertissements lorsque certains types d’assignations et de comparaisons sont effectués en utilisant une combinaison de types numériques et de types booléens. Ces deux utilisations finissent par se heurter lorsqu’on travaille avec du code hérité.

Pour contourner ce problème, certains développeurs utilisent l'identité booléenne suivante: !num_value renvoie bool true if num_value == 0; false autrement. !!num_value renvoie bool false si num_value == 0; true autrement. La négation simple est suffisante pour convertir num_value en bool; Cependant, la double négation est nécessaire pour rétablir le sens original de l'expression booléenne. 

Ce modèle est connu sous le nom de idiome , c’est-à-dire quelque chose couramment utilisé par des personnes familiarisées avec le langage. Par conséquent, je ne le vois pas comme un anti-motif, autant que je le ferais static_cast<bool>(num_value). La distribution peut très bien donner les résultats corrects, mais certains compilateurs émettent alors un avertissement de performance, vous devez donc toujours vous en occuper. 

L'autre façon de résoudre ce problème est de dire (num_value != FALSE). Je suis d'accord avec ça aussi, mais dans l'ensemble, !!num_value est beaucoup moins bavard, peut-être plus clair, et ne crée pas de confusion la deuxième fois que vous le voyez. 

2
KarlU

!! était utilisé pour faire face au C++ original qui n'avait pas de type booléen (comme C non plus). 


Exemple de problème:

Dans if(condition), la condition doit être évaluée avec un type tel que double, int, void*, etc., mais pas bool car elle n'existe pas encore.

Supposons qu'une classe existait int256 (un entier de 256 bits) et que toutes les conversions/conversions d'entiers étaient surchargées.

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

Pour vérifier si x était "vrai" ou différent de zéro, if (x) convertirait x en un entier et then évaluerait si int était différent de zéro. Une surcharge typique de (int) x renverrait uniquement les LSbits de x. if (x) ne testait alors que les LSbits de x.

Mais C++ a l'opérateur !. Un !x surchargé évalue généralement tous les bits de x. Donc, pour revenir à la logique non inversée, on utilise if (!!x).

Ref Les anciennes versions de C++ utilisaient-elles l'opérateur `int` d'une classe lors de l'évaluation de la condition dans une instruction` if () `?

1
chux

Comme Marcin mentionné, cela pourrait avoir de l'importance si une surcharge d'opérateur est en jeu. Sinon, en C/C++, cela n'a pas d'importance, sauf si vous effectuez l'une des opérations suivantes:

  • comparaison directe avec true (ou en C quelque chose qui ressemble à une macro TRUE,), ce qui est presque toujours une mauvaise idée. Par exemple:

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

  • vous voulez simplement que quelque chose soit converti en une valeur 0/1 stricte. En C++, une affectation à un bool le fera de manière implicite (pour les choses implicitement convertibles en bool). En C ou si vous avez affaire à une variable non-bool, c'est un idiome que j'ai vu, mais je préfère la variété (some_variable != 0) moi-même.

Je pense que dans le contexte d’une expression booléenne plus large, cela ne fait qu’encombrer les choses.

1
Michael Burr

Si variable est de type objet, il pourrait avoir un! opérateur défini mais non transtypé (ou pire, transtyple implicite vers int avec une sémantique différente). L'appel de l'opérateur! à deux reprises entraîne une conversion en bool qui fonctionne même dans des cas étranges.

1
Joshua

Ceci peut être un exemple de l’astuce double-bang, voir L’idiome Safe Bool pour plus de détails Ici, je résume la première page de l'article.

En C++, il existe plusieurs façons de fournir des tests booléens pour les classes. 

Un moyen évident est l'opérateur de conversion operator bool.

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

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

Nous pouvons tester la classe,

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

Cependant, opereator bool est considéré comme dangereux car il autorise des opérations non sensées telles que test << 1; ou int i=test

Utiliseroperator! est plus sûr car nous évitons les problèmes de conversion implicite ou de surcharge.

La mise en œuvre est triviale,

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

Les deux manières idiomatiques de tester un objet Testable sont

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

La première version if (!!test) est ce que certaines personnes appellent l’astuce double-bang.

0
sam

C'est correct, mais en C, inutile ici - 'if' et '&&' traiteraient l'expression de la même manière sans le '!!'.

Je suppose que la raison de faire cela en C++ est que '&&' pourrait être surchargé. Mais alors, donc "!" Pourrait donc, donc vraiment ne vous garantit pas un bool, sans regarder le code pour les types de variable et api.call. Peut-être que quelqu'un avec plus d'expérience en C++ pourrait expliquer; peut-être s'agit-il d'une mesure de défense en profondeur, et non d'une garantie.

0
Darius Bacon

Peut-être que les programmeurs pensaient quelque chose comme ça ...

!! myAnswer est booléen. Dans le contexte, cela devrait devenir booléen, mais j'adore cogner des trucs pour en être sûr, car il était une fois un microbe mystérieux qui m'a mordu, et bang bang, je l'ai tué.

0
dongilmore