c++ - sur - qu est ce que la négation




Double négation dans le code C++ (10)

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

La raison de faire cela en C ++, je suppose, est que '&&' pourrait être surchargé. Mais alors, ça pourrait '!', Donc ça ne vous garantit pas vraiment 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 que c'est une sorte de mesure de défense en profondeur, pas une garantie.

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

Je m'occupe surtout de 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 ++, ma seule estimation de la raison pour laquelle ils le font est qu'ils veulent rendre absolument positif que la valeur évaluée est la représentation booléenne réelle. Alors ils le nient, puis le nient de nouveau pour le ramener à sa valeur booléenne réelle.

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


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 de manière 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 général d'utiliser simplement le !! idiome.

Ils pourraient probablement faire la même chose en comparant avec 0, mais à mon avis, c'est en fait plus simple de faire la double négation, puisque c'est le plus proche d'un cast-to-bool que C a.

Ce code peut aussi être utilisé en C ++ ... c'est une chose du plus petit dénominateur commun. Si possible, faites ce qui fonctionne en C et C ++.


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

Un code IMO comme celui-ci n'a pas sa place dans les systèmes qui doivent être maintenus - parce que ce n'est pas un code immédiatement lisible (d'où la question en premier lieu).

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


Cela peut être un exemple de l' astuce double-bang , 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.

Une façon évidente est l' operator bool conversion 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, opereator bool est considéré comme dangereux car il permet des opérations absurdes 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 triviale,

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

Les deux manières idiomatiques de tester l'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 le trick double-bang .


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


Il contourne un avertissement de 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 "forcing value to bool 'true" ou "false" (avertissement de performance) "sur MSVC ++, mais la ligne' baz 'se faufile bien.


Les développeurs hérités C n'avaient pas de type booléen, donc ils #define TRUE 1 souvent #define TRUE 1 et #define FALSE 0 , puis utilisaient des types de données numériques arbitraires pour les comparaisons booléennes. Maintenant que nous avons bool , beaucoup de compilateurs vont émettre des avertissements quand certains types d'assignations et de comparaisons sont faites en utilisant un mélange de types numériques et de types booléens. Ces deux utilisations finiront par entrer en collision lorsque vous utiliserez un code existant.

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 sinon. La négation simple 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 comme un idiome , c'est-à-dire quelque chose couramment utilisé par des personnes familières avec la langue. Par conséquent, je ne le vois pas comme un anti-pattern, 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, donc vous devez toujours y remédier.

L'autre façon d'aborder ceci est de dire, (num_value != FALSE) . Je suis d'accord avec ça aussi, mais dans l'ensemble, !!num_value est beaucoup moins verbeux, peut-être plus clair, et n'est pas déroutant la deuxième fois que vous le voyez.


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


Si la variable est de type objet, elle peut avoir un! opérateur défini mais pas cast à booléen (ou pire un cast implicite en int avec des sémantiques différentes) Appeler deux fois l'opérateur! aboutit à un converti en booléen qui fonctionne même dans des cas étranges.


!! a été utilisé pour gérer le C ++ original qui n'avait pas de type booléen (comme C non plus).

Exemple de problème:

À l'intérieur if(condition) , la condition doit être évaluée à un type comme double, int, void* , etc., mais pas bool car il n'existe pas encore.

Supposons qu'une classe existe int256 (un entier de 256 bits) et que toutes les conversions entières / conversions entières ont été surchargées.

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

Pour tester si x était "vrai" ou différent de zéro, if (x) convertirait x en un entier puis évaluer si cet entier était non nul. Une surcharge typique de (int) x ne retournerait que les LSbits de x . if (x) testait alors seulement les LSbits de x .

Mais C ++ a le ! opérateur. Une surcharge !x évaluerait 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 () `?







boolean