Double négation en code C ++

Je suis juste venu sur un projet avec une base de code assez énorme.

Je m’occupe principalement de C ++ et une grande partie du code qu’ils écrivent utilise une double négation pour leur logique booléenne.

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

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

Je ne suis pas un expert C ++ chevronné, mais ma seule supposition est qu’ils veulent absolument être sûr que la valeur évaluée est la représentation booléenne réelle. Donc, ils le nient, puis le nient à nouveau pour le ramener à sa valeur booléenne réelle.

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

C’est un truc pour convertir en bool.

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

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

Pourquoi doivent-ils le faire? __builtin_expect de GCC traite ses parameters comme étant long et non bool , il doit donc y avoir une forme de conversion. Comme ils ne savent pas ce que c’est quand ils écrivent ces macros, il est plus simple d’utiliser simplement le !! 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, puisque c’est la plus proche d’une dissortingbution à une bool que C a.

Ce code peut aussi être utilisé en C ++ … c’est le plus petit dénominateur commun. Si possible, faites ce qui fonctionne à la fois en C et en C ++.

Les codeurs pensent qu’il convertira l’opérande en booléen, mais parce que les opérandes de && sont déjà implicitement convertis en bool, c’est complètement redondant.

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

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

Le code IMO comme celui-ci n’a pas sa place dans les systèmes qui doivent être maintenus – car ce n’est pas du code lisible immédiatement (d’où la question en premier lieu).

Le code doit être lisible – sinon vous laisserez un inheritance de temps pour l’avenir – car il faut du temps pour comprendre quelque chose d’inutile.

Il met en garde un avertissement du compilateur. Essaye ça:

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

La ligne ‘bar’ génère une “valeur de forçage pour booler ‘true’ ou ‘false’ (avertissement de performance)” sur MSVC ++, mais la ligne ‘baz’ passe bien.

Est opérateur! surchargé?
Sinon, ils le font probablement pour convertir la variable en booléen sans générer d’avertissement. Ce n’est certainement pas une manière standard de faire les choses.

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’affectations et de comparaisons seront effectués en utilisant un mélange de types numériques et de types booléens. Ces deux utilisations vont éventuellement se heurter lorsque vous travaillez avec du code hérité.

Pour contourner ce problème, certains développeurs utilisent l’identité booléenne suivante !num_value renvoie bool true si num_value == 0 ; false sinon. !!num_value renvoie bool false si num_value == 0 ; true autrement. La négation unique est suffisante pour convertir num_value en bool ; cependant, la double négation est nécessaire pour restaurer le sens originel de l’expression booléenne.

Ce modèle est connu sous le nom d’ idiome , c’est-à-dire quelque chose qui est couramment utilisé par les personnes familiarisées avec la langue. Par conséquent, je ne le vois pas comme un anti-pattern, autant que je le ferais pour static_cast(num_value) . Le casting peut très bien donner des résultats corrects, mais certains compilateurs émettent alors un avertissement de performance, vous devez donc toujours y remédier.

L’autre façon de traiter cela est de dire (num_value != FALSE) . Je suis d’accord avec ça, mais dans l’ensemble !!num_value est beaucoup moins verbeux, peut-être plus clair et ne prête pas à confusion la deuxième fois que vous le voyez.

Comme Marcin l’a mentionné, il peut être important que la surcharge de l’opérateur soit en jeu. Sinon, en C / C ++, peu importe, sauf si vous faites l’une des choses suivantes:

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

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

  • vous voulez simplement quelque chose converti en une valeur 0/1 ssortingcte. En C ++, une assignation à un bool fera implicitement (pour les choses qui sont implicitement convertibles en bool ). Dans C ou si vous avez affaire à une variable non bool, c’est un idiome que j’ai vu, mais je préfère la (some_variable != 0) moi-même.

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

Si variable est du type object, il pourrait avoir un! opérateur défini mais pas de conversion en booléen (ou pire transtypage implicite en int de sémantique différente. Appeler l’opérateur! à deux resockets donne lieu à une conversion en booléen qui fonctionne même dans des cas étranges.

!! a été utilisé pour faire face à C ++ d’origine qui n’avait pas un type booléen (comme ne le fait pas non plus).


Exemple problème:

Inside if(condition) , la condition doit être évaluée à un type comme 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 tester si x était “vrai” ou non nul, if (x) convertirait x en un nombre entier, puis évaluerait si cet int était non nul. Une surcharge typique de (int) x ne renverrait que les LSbits de x . if (x) testait alors uniquement les LSbits de x .

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

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 () `?

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, ” peut tout aussi bien fonctionner, donc cela ne garantit pas vraiment que vous obtenez un booléen, 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 cela signifie-t-il une mesure de défense en profondeur, pas une garantie.

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 bang bang bang pour être sûr, parce qu’il était une fois un mystérieux bug qui m’a mordu, et bang bang, je l’ai tué.

Cela peut être un exemple du double-coup , voir The Safe Bool Idiom 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’ operator bool conversion d’opérateur 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, l' opereator bool est considéré comme dangereux car il permet des opérations non sensuelles telles que le test << 1; ou int i=test .

Utilisation de l' operator! est plus sûr car nous évitons les problèmes de conversion ou de surcharge implicites.

L'implémentation est sortingviale,

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

Les deux manières idiomatiques de tester un object 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 le tour du double coup .