Double négation en C ++


124

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

Je travaille 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 types sont des programmeurs intelligents, il est évident qu'ils ne le font pas par accident.

Je ne suis pas un expert C ++ chevronné, ma seule estimation de la raison pour laquelle ils font cela est qu'ils veulent s'assurer que la valeur évaluée est la représentation booléenne réelle. 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?



Ce sujet a été discuté ici .
Dima

Réponses:


121

C'est une astuce pour convertir en booléen.


19
Je pense que le cast explicitement avec (bool) serait plus clair, pourquoi utiliser ce délicat !!, car il est moins typé?
Baiyan Huang

27
Cependant, c'est inutile en C ++ ou en C moderne, ou lorsque le résultat n'est utilisé que dans une expression booléenne (comme dans la question). C'était utile lorsque nous n'avions pas de booltype, pour éviter de stocker des valeurs autres que 1et 0dans des variables booléennes.
Mike Seymour

6
@lzprgmr: le cast explicite provoque un "avertissement de performance" sur MSVC. En utilisant !!ou !=0résout le problème, et parmi les deux, je trouve l'ancien nettoyeur (car il fonctionnera sur un plus grand nombre de types). Je reconnais également qu'il n'y a aucune raison d'utiliser l'un ou l'autre dans le code en question.
Yakov Galka

6
@Noldorin, je pense que cela améliore la lisibilité - si vous savez ce que cela signifie, c'est simple, soigné et logique.
jwg

19
S'améliore? Putain de merde ... donne-moi un peu de ce que tu fumes.
Noldorin le

73

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? GCC __builtin_expecttraite ses paramètres comme longet non bool, il doit donc y avoir une forme de conversion. Comme ils ne savent pas ce que condc'est quand ils écrivent ces macros, il est plus général 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 plus proche d'un cast-to-bool que C a.

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


Je pense que cela a beaucoup de sens quand on y réfléchit. Je n'ai pas lu toutes les réponses, mais il semble que le processus de conversion ne soit pas spécifié. Si nous avons une valeur avec 2 bits de haut et avec une valeur qui n'a qu'un bit de haut, nous aurons une valeur non nulle. La négation d'une valeur non nulle entraîne une conversion booléenne (false si elle est nulle, true sinon). Ensuite, la négation à nouveau aboutit à un booléen qui représente la vérité originale.
Joey Carson

Puisque SO ne me permettra pas de mettre à jour mon commentaire, j'ajouterai un correctif à mon erreur. La négation d'une valeur intégrale entraîne une conversion booléenne (false si elle est différente de zéro, true sinon).
Joey Carson

51

Les codeurs pensent qu'il convertira l'opérande en booléen, mais comme les opérandes de && sont déjà implicitement convertis en booléen, il est totalement redondant.


14
Visual C ++ donne une baisse des performances dans certains cas sans cette astuce.
Kirill V. Lyadvinsky

1
Je suppose qu'il serait préférable de désactiver simplement l'avertissement plutôt que de contourner les avertissements inutiles dans le code.
Ruslan

Peut-être qu'ils ne s'en rendent pas compte. Cela a cependant un sens parfait dans le contexte d'une macro, où vous pourriez travailler avec des types de données intégraux dont vous n'êtes pas au courant. Considérez les objets dont l'opérateur entre parenthèses est surchargé pour renvoyer une valeur intégrale représentant un champ de bits.
Joey Carson

12

C'est une technique pour éviter d'écrire (variable! = 0) - c'est-à-dire pour convertir de quelque type que ce soit en booléen.

Un code IMO comme celui-ci n'a pas sa place dans les systèmes qui doivent être maintenus - car 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 temporelle pour l'avenir - car il faut du temps pour comprendre quelque chose qui est inutilement alambiqué.


8
Ma définition d'un truc est quelque chose que tout le monde ne peut pas comprendre en première lecture. Quelque chose à découvrir est une astuce. Aussi horrible parce que le! l'opérateur pourrait être surchargé ...
Richard Harrison

6
@ orlandu63: le typage simple est bool(expr): il fait la bonne chose et tout le monde comprend l'intention à première vue. !!(expr)est une double négation, qui se convertit accidentellement en booléen ... ce n'est pas simple.
Adrien Plisson le

12

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


9

Il évite 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 à booléen 'true' ou 'false' (avertissement de performance)» sur MSVC ++, mais la ligne 'baz' se faufile très bien.


1
Le plus souvent rencontré dans l'API Windows elle-même qui ne connaît pas le booltype - tout est codé en tant que 0ou 1dans un fichier int.
Mark Ransom

4

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


4

Les développeurs C avaient hérité aucun type Boolean, ils souvent #define TRUE 1et #define FALSE 0puis utiliser les types de données numériques arbitraires pour des comparaisons booléennes. Maintenant que nous l'avons fait bool, de nombreux compilateurs émettront des avertissements lorsque certains types d'affectations et de comparaisons sont effectués en utilisant un mélange de types numériques et de types booléens. Ces deux usages finiront par entrer en conflit lorsque vous travaillez avec du code hérité.

Pour contourner ce problème, certains développeurs utilisent l'identité booléenne suivante: !num_valuerenvoie bool trueif num_value == 0; falseautrement. !!num_valuerenvoie bool falsesi num_value == 0; trueautrement. La négation seule est suffisante pour convertir num_valueà bool; cependant, la double négation est nécessaire pour restaurer le sens original de l'expression booléenne.

Ce modèle est connu comme un idiome , c'est-à-dire quelque chose de communément utilisé par des personnes familières avec la langue. Par conséquent, je ne le vois pas comme un anti-modèle, autant que je le voudrais static_cast<bool>(num_value). La distribution pourrait très bien donner les résultats corrects, mais certains compilateurs émettent alors un avertissement de performances, vous devez donc toujours résoudre ce problème.

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


2

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


Exemple de problème:

À l'intérieur if(condition), les conditionbesoins d'évaluer à un type comme double, int, void*, etc., mais pas boolcar il n'existe pas encore.

Supposons qu'une classe existe int256(un entier de 256 bits) et que toutes les conversions / transtypages d'entiers ont été surchargés.

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

Pour tester si xétait "vrai" ou non nul, if (x)convertirait xen un entier et ensuite évaluer si ce intn'était pas zéro. Une surcharge typique de (int) xrenverrait uniquement les LSbits de x. if (x)testait alors uniquement les LSbits de x.

Mais C ++ a l' !opérateur. Un surchargé !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 () `?


1

Comme Marcin l'a mentionné, cela pourrait bien avoir une importance si la surcharge des opérateurs est en jeu. Sinon, en C / C ++, cela n'a pas d'importance sauf si vous faites l'une des choses suivantes:

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

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

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

Je pense que dans le contexte d'une expression booléenne plus large, cela encombre simplement les choses.


1

Si la variable est de type objet, elle peut avoir un! opérateur défini mais pas de conversion en bool (ou pire, une conversion implicite en int avec une sémantique différente. L'appel de l'opérateur! deux fois entraîne une conversion en bool qui fonctionne même dans des cas étranges.


0

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, '!' Pourrait l'être aussi, donc cela ne garantit pas vraiment que vous obteniez un booléen, sans regarder le code pour les types de variableet api.call. Peut-être que quelqu'un avec plus d'expérience C ++ pourrait expliquer; il s'agit peut-être d'une sorte de mesure de défense en profondeur, et non d'une garantie.


Le compilateur traiterait les valeurs de la même manière si elle est uniquement utilisée comme opérande d'un ifou &&, mais l'utilisation !!peut aider sur certains compilateurs si elle if (!!(number & mask))est remplacée par bit triggered = !!(number & mask); if (triggered); sur certains compilateurs embarqués avec des types de bits, attribuer par exemple 256 à un type de bit donnera zéro. Sans le !!, la transformation apparemment sûre (copie de la ifcondition dans une variable puis branchement) ne sera pas sûre.
supercat

0

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


0

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

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

// 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 boolest considéré comme dangereux car il permet des opérations absurdes telles que test << 1;ou int i=test.

L'utilisation operator!est plus sûre 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 Testableobjet 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 certains appellent le truc du double bang .


1
Depuis C ++ 11, on peut utiliser explicit operator boolpour empêcher la conversion implicite vers d'autres types intégraux.
Arne Vogel
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.