Doppelte Negation in C ++

124

Ich bin gerade auf ein Projekt mit einer ziemlich großen Codebasis gestoßen.

Ich beschäftige mich hauptsächlich mit C ++ und ein Großteil des Codes, den sie schreiben, verwendet doppelte Negation für ihre boolesche Logik.

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

Ich weiß, dass diese Leute intelligente Programmierer sind. Es ist offensichtlich, dass sie dies nicht zufällig tun.

Ich bin kein erfahrener C ++ - Experte. Meine einzige Vermutung, warum sie dies tun, ist, dass sie absolut sicher stellen möchten, dass der zu bewertende Wert die tatsächliche boolesche Darstellung ist. Also negieren sie es und negieren es dann erneut, um es wieder auf seinen tatsächlichen booleschen Wert zu bringen.

Ist das richtig oder fehlt mir etwas?

Brian Gianforcaro
quelle
Dieses Thema wurde diskutiert hier .
Dima

Antworten:

121

Es ist ein Trick, in Bool zu konvertieren.

Don Neufeld
quelle
19
Ich denke, es explizit mit (bool) zu besetzen wäre klarer, warum sollte man das knifflig machen !!, weil es weniger tippt?
Baiyan Huang
27
Es ist jedoch in C ++ oder modernem C sinnlos oder wenn das Ergebnis nur in einem booleschen Ausdruck verwendet wird (wie in der Frage). Es war nützlich, als wir noch keinen boolTyp hatten, um das Speichern anderer Werte als 1und 0in booleschen Variablen zu vermeiden .
Mike Seymour
6
@lzprgmr: Explizite Umwandlung verursacht eine "Leistungswarnung" bei MSVC. Verwenden !!oder !=0lösen Sie das Problem, und unter den beiden finde ich den ehemaligen Reiniger (da er bei einer größeren Anzahl von Typen funktioniert). Ich stimme auch zu, dass es keinen Grund gibt, den fraglichen Code zu verwenden.
Yakov Galka
6
@Noldorin, ich denke, es verbessert die Lesbarkeit - wenn Sie wissen, was es bedeutet, ist es einfach, ordentlich und logisch.
JWG
19
Verbessert? Verdammt noch mal ... gib mir etwas von dem, was du rauchst.
Noldorin
73

In einigen Zusammenhängen ist es tatsächlich eine sehr nützliche Redewendung. Nehmen Sie diese Makros (Beispiel aus dem Linux-Kernel). Für GCC werden sie wie folgt implementiert:

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

Warum müssen sie das tun? GCCs __builtin_expectbehandelt seine Parameter als longund nicht bool, daher muss es eine Form der Konvertierung geben. Da sie nicht wissen, was condist, wenn sie diese Makros schreiben, ist es am allgemeinsten, einfach die !!Redewendung zu verwenden.

Sie könnten wahrscheinlich das Gleiche tun, indem sie mit 0 vergleichen, aber meiner Meinung nach ist es tatsächlich einfacher, die doppelte Negation durchzuführen, da dies einem Cast-to-Bool am nächsten kommt, den C hat.

Dieser Code kann auch in C ++ verwendet werden ... es ist eine Sache mit dem kleinsten gemeinsamen Nenner. Wenn möglich, machen Sie das, was sowohl in C als auch in C ++ funktioniert.

Tom Barta
quelle
Ich denke, das macht sehr viel Sinn, wenn man darüber nachdenkt. Ich habe nicht jede Antwort gelesen, aber es scheint, dass der Konvertierungsprozess nicht angegeben ist. Wenn wir einen Wert mit 2 Bit hoch und einen Wert mit nur einem Bit hoch haben, haben wir einen Wert ungleich Null. Das Negieren eines Werts ungleich Null führt zu einer booleschen Konvertierung (false, wenn es Null ist, andernfalls true). Wenn Sie dann erneut negieren, erhalten Sie einen Booleschen Wert, der die ursprüngliche Wahrheit darstellt.
Joey Carson
Da SO es mir nicht erlaubt, meinen Kommentar zu aktualisieren, werde ich meinen Fehler beheben. Das Negieren eines Integralwerts führt zu einer booleschen Konvertierung (false, wenn er nicht Null ist, andernfalls true).
Joey Carson
51

Die Codierer glauben, dass der Operand in Bool konvertiert wird, aber da die Operanden von && bereits implizit in Bool konvertiert sind, ist er absolut redundant.

Sprudler
quelle
14
Visual C ++ führt in einigen Fällen ohne diesen Trick zu einem Leistungsabfall.
Kirill V. Lyadvinsky
1
Ich würde annehmen, dass es am besten ist, die Warnung einfach zu deaktivieren, als nutzlose Warnungen im Code zu umgehen.
Ruslan
Vielleicht merken sie es nicht. Dies ist jedoch im Kontext eines Makros durchaus sinnvoll, in dem Sie möglicherweise mit integralen Datentypen arbeiten, die Sie nicht kennen. Betrachten Sie Objekte mit überladenem Klammeroperator, um einen Integralwert zurückzugeben, der ein Bitfeld darstellt.
Joey Carson
12

Es ist eine Technik, um das Schreiben zu vermeiden (Variable! = 0) - dh um von einem beliebigen Typ in einen Bool zu konvertieren.

IMO-Code wie dieser hat keinen Platz in Systemen, die gewartet werden müssen - weil es sich nicht um sofort lesbaren Code handelt (daher die Frage an erster Stelle).

Code muss lesbar sein - andernfalls hinterlassen Sie ein Zeitschulden-Erbe für die Zukunft -, da es Zeit braucht, um etwas zu verstehen, das unnötig verwickelt ist.

Richard Harrison
quelle
8
Meine Definition eines Tricks ist etwas, das nicht jeder in der ersten Lesung verstehen kann. Etwas, das herausgefunden werden muss, ist ein Trick. Auch schrecklich, weil die! Betreiber könnte überlastet sein ...
Richard Harrison
6
@ orlandu63: einfaches typecasting ist bool(expr): es macht das richtige und jeder versteht die absicht auf den ersten blick. !!(expr)ist eine doppelte Negation, die versehentlich in bool umgewandelt wird ... das ist nicht einfach.
Adrien Plisson
12

Ja, es ist richtig und nein, Sie vermissen nichts. !!ist eine Konvertierung in Bool. Weitere Informationen finden Sie in dieser Frage .

jwfearn
quelle
9

Eine Compiler-Warnung wird umgangen. Versuche dies:

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

Die 'bar'-Zeile generiert unter MSVC ++ einen "Forcing-Wert zum Boolen von' true 'oder' false '(Leistungswarnung)", aber die' baz'-Zeile schleicht sich gut durch.

RobH
quelle
1
Am häufigsten in der Windows-API selbst anzutreffen, die den boolTyp nicht kennt - alles ist als 0oder 1in einem codiert int.
Mark Ransom
4

Ist Betreiber! überladen?
Wenn nicht, tun sie dies wahrscheinlich, um die Variable in einen Bool zu konvertieren, ohne eine Warnung zu erzeugen. Dies ist definitiv keine Standardmethode.

Marcin
quelle
4

Legacy - C - Entwickler hatten keinen Booleschen Typen, so dass sie oft #define TRUE 1und #define FALSE 0dann beliebige numerische Datentypen für Boolesche Vergleiche verwendet. Jetzt, da wir es haben bool, werden viele Compiler Warnungen ausgeben, wenn bestimmte Arten von Zuweisungen und Vergleichen unter Verwendung einer Mischung aus numerischen Typen und booleschen Typen durchgeführt werden. Diese beiden Verwendungen kollidieren schließlich, wenn Sie mit Legacy-Code arbeiten.

Um dieses Problem zu umgehen, verwenden einige Entwickler die folgende boolesche Identität: !num_valuegibt bool trueif zurück num_value == 0; falseAndernfalls. !!num_valuegibt bool falseif zurück num_value == 0; trueAndernfalls. Die einzelne Negation reicht aus, um num_valuezu konvertieren bool; Die doppelte Negation ist jedoch erforderlich, um den ursprünglichen Sinn des Booleschen Ausdrucks wiederherzustellen.

Dieses Muster ist als Redewendung bekannt , dh etwas, das häufig von Personen verwendet wird, die mit der Sprache vertraut sind. Daher sehe ich es nicht so sehr als Anti-Muster, wie ich es tun würde static_cast<bool>(num_value). Die Besetzung liefert möglicherweise die richtigen Ergebnisse, aber einige Compiler geben dann eine Leistungswarnung aus, sodass Sie sich noch darum kümmern müssen.

Der andere Weg, dies anzugehen, ist zu sagen , (num_value != FALSE). Ich bin auch damit einverstanden, aber alles in allem !!num_valueist es weit weniger ausführlich, kann klarer sein und ist nicht verwirrend, wenn Sie es das zweite Mal sehen.

KarlU
quelle
2

!! wurde verwendet, um mit ursprünglichem C ++ umzugehen, das keinen booleschen Typ hatte (wie auch C nicht).


Beispiel Problem:

Im Inneren if(condition), die conditionauf die Bedürfnisse bis zu einem gewissen Art wie zu beurteilen double, int, void*, etc., aber nicht , boolda es noch nicht vorhanden.

Angenommen, eine Klasse existiert int256(eine 256-Bit-Ganzzahl) und alle Ganzzahlkonvertierungen / -umwandlungen wurden überladen.

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

Um zu testen, ob x"wahr" oder nicht Null ist, if (x)würde xin eine ganze Zahl konvertiert und dann bewertet, ob dies intnicht Null ist. Eine typische Überladung von (int) xwürde nur die LSbits von zurückgeben x. if (x)testete dann nur die LSbits von x.

Aber C ++ hat den !Operator. Eine Überlastung !xwürde typischerweise alle Bits von auswerten x. Es wird also verwendet, um zur nicht invertierten Logik zurückzukehren if (!!x).

Ref Haben ältere Versionen von C ++ den Operator "int" einer Klasse verwendet, als sie die Bedingung in einer "if ()" - Anweisung ausgewertet haben?

chux - Monica wieder einsetzen
quelle
1

Wie Marcin erwähnte, könnte es durchaus wichtig sein, ob eine Überlastung des Bedieners im Spiel ist. Andernfalls spielt es in C / C ++ keine Rolle, außer wenn Sie eines der folgenden Dinge tun:

  • direkter Vergleich mit true(oder in C so etwas wie einem TRUEMakro), was fast immer eine schlechte Idee ist. Beispielsweise:

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

  • Sie möchten einfach etwas in einen strengen 0/1-Wert konvertieren. In C ++ boolwird dies implizit durch eine Zuweisung an a ausgeführt (für die Dinge, in die implizit konvertierbar ist bool). In C oder wenn Sie es mit einer Nicht-Bool-Variablen zu tun haben, ist dies eine Redewendung, die ich gesehen habe, aber ich bevorzuge die (some_variable != 0)Variante selbst.

Ich denke, im Kontext eines größeren booleschen Ausdrucks werden die Dinge einfach durcheinander gebracht.

Michael Burr
quelle
1

Wenn die Variable vom Objekttyp ist, hat sie möglicherweise ein! Operator definiert, aber keine Umwandlung in Bool (oder schlimmer noch eine implizite Umwandlung in Int mit unterschiedlicher Semantik. Wenn Sie den Operator! zweimal aufrufen, wird eine Konvertierung in Bool durchgeführt, die auch in seltsamen Fällen funktioniert.

Joshua
quelle
0

Es ist richtig, aber in C hier sinnlos - 'if' und '&&' würden den Ausdruck ohne das '!!' genauso behandeln.

Der Grund dafür in C ++ ist vermutlich, dass '&&' überladen sein könnte. Aber dann könnte '!', So dass es nicht wirklich garantiert, dass Sie einen Bool bekommen, ohne den Code für die Typen von variableund zu überprüfen api.call. Vielleicht könnte jemand mit mehr C ++ - Erfahrung erklären; Vielleicht ist es eine tiefgreifende Verteidigungsmaßnahme, keine Garantie.

Darius Bacon
quelle
Der Compiler würde die Werte in beiden Fällen gleich behandeln, wenn sie nur als Operand für ein ifoder verwendet &&werden. Die Verwendung !!kann jedoch bei einigen Compilern hilfreich sein, wenn sie if (!!(number & mask))durch ersetzt werden bit triggered = !!(number & mask); if (triggered). Bei einigen eingebetteten Compilern mit Bittypen ergibt die Zuweisung von z. B. 256 zu einem Bittyp Null. Ohne die !!ist die scheinbar sichere Transformation (Kopieren der ifBedingung in eine Variable und anschließendes Verzweigen) nicht sicher.
Supercat
0

Vielleicht haben die Programmierer so etwas gedacht ...

!! myAnswer ist boolesch. Im Kontext sollte es boolesch werden, aber ich liebe es einfach, Dinge zu knallen, um sicherzugehen, denn es war einmal ein mysteriöser Fehler, der mich gebissen hat, und ich habe ihn getötet.

Dongilmore
quelle
0

Dies kann ein Beispiel für den Double-Bang-Trick sein. Weitere Informationen finden Sie unter The Safe Bool Idiom . Hier fasse ich die erste Seite des Artikels zusammen.

In C ++ gibt es eine Reihe von Möglichkeiten, Boolesche Tests für Klassen bereitzustellen.

Ein offensichtlicher Weg ist der operator boolKonvertierungsoperator.

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

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

Wir können die Klasse testen,

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

Wird opereator booljedoch als unsicher angesehen, da es unsinnige Operationen wie test << 1;oder zulässt int i=test.

Die Verwendung operator!ist sicherer, da implizite Konvertierungs- oder Überlastungsprobleme vermieden werden.

Die Implementierung ist trivial,

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

Die zwei idiomatischen Möglichkeiten zum Testen von TestableObjekten sind

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

Die erste Version if (!!test)ist das, was manche Leute den Doppelknall-Trick nennen .

kgf3JfUtW
quelle
1
Seit C ++ 11 kann explicit operator booldamit die implizite Konvertierung in andere Integraltypen verhindert werden.
Arne Vogel