Podwójna negacja w C ++


124

Właśnie przyszedłem do projektu z dość dużą bazą kodu.

Zajmuję się głównie C ++ i wiele z ich kodu używa podwójnej negacji dla logiki boolowskiej.

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

Wiem, że ci faceci to inteligentni programiści, oczywiste jest, że nie robią tego przez przypadek.

Nie jestem doświadczonym ekspertem od C ++, moje jedyne przypuszczenie, dlaczego to robią, to to, że chcą mieć absolutną pewność, że oceniana wartość jest rzeczywistą reprezentacją boolowską. Więc negują to, a następnie ponownie negują, aby przywrócić mu rzeczywistą wartość logiczną.

Czy to prawda, czy czegoś mi brakuje?



Ten temat został omówiony tutaj .
Dima,

Odpowiedzi:


121

Konwersja na bool to sztuczka.


19
Myślę, że rzucanie tego jawnie za pomocą (bool) byłoby jaśniejsze, po co używać tego trudnego !!, ponieważ wymaga mniej pisania?
Baiyan Huang

27
Jednak jest to bezcelowe w C ++ lub nowoczesnym C, lub gdy wynik jest używany tylko w wyrażeniu logicznym (jak w pytaniu). To była przydatna powrotem, gdy nie mieliśmy booltyp, aby uniknąć przechowywania wartości inne niż 1i 0zmiennych logicznych.
Mike Seymour

6
@lzprgmr: jawne rzutowanie powoduje „ostrzeżenie o wydajności” na MSVC. Używając !!lub !=0rozwiązuje problem, a wśród tych dwóch znajduję poprzedni środek czyszczący (ponieważ będzie działał na większej liczbie typów). Zgadzam się również, że w omawianym kodzie nie ma powodu, aby go używać.
Yakov Galka

6
@Noldorin, myślę, że to poprawia czytelność - jeśli wiesz, co to znaczy, jest proste, schludne i logiczne.
jwg

19
Poprawia? Do diabła ... daj mi trochę tego, co palisz.
Noldorin

73

Właściwie jest to bardzo przydatny idiom w niektórych kontekstach. Weźmy te makra (przykład z jądra Linuksa). W przypadku GCC są one zaimplementowane w następujący sposób:

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

Dlaczego muszą to robić? GCC __builtin_expecttraktuje swoje parametry jako longa nie bool, więc potrzebna jest jakaś forma konwersji. Ponieważ nie wiedzą, co condjest, kiedy piszą te makra, najogólniej jest po prostu użyć !!idiomu.

Prawdopodobnie mogliby zrobić to samo, porównując z 0, ale moim zdaniem łatwiej jest zrobić podwójną negację, ponieważ jest to najbliższe rzutowaniu na bool, które ma C.

Ten kod może być również używany w C ++ ... to rzecz o najniższym wspólnym mianowniku. Jeśli to możliwe, zrób to, co działa zarówno w C, jak i C ++.


Myślę, że to ma sens, kiedy się nad tym zastanowić. Nie przeczytałem wszystkich odpowiedzi, ale wygląda na to, że proces konwersji nie został określony. Jeśli mamy wartość o wysokości 2 bity i wartość, która ma tylko jeden bit, otrzymamy wartość niezerową. Negacja wartości niezerowej skutkuje konwersją logiczną (fałsz, jeśli wynosi zero, w przeciwnym razie prawda). Następnie ponowne zanegowanie skutkuje wartością logiczną, która reprezentuje oryginalną prawdę.
Joey Carson,

Ponieważ SO nie pozwala mi zaktualizować mojego komentarza, dodam poprawkę do mojego błędu. Negacja wartości całkowitej powoduje konwersję logiczną (fałsz, jeśli jest różny od zera, w przeciwnym razie prawda).
Joey Carson,

51

Programiści myślą, że przekonwertuje operand na bool, ale ponieważ operandy && są już niejawnie konwertowane na bool, jest to całkowicie zbędne.


14
Visual C ++ daje w niektórych przypadkach spadek wydajności bez tej sztuczki.
Kirill V. Lyadvinsky

1
Przypuszczam, że najlepiej byłoby po prostu wyłączyć ostrzeżenie, niż obejść bezużyteczne ostrzeżenia w kodzie.
Ruslan

Być może nie zdają sobie z tego sprawy. Ma to jednak sens w kontekście makra, w którym możesz pracować z integralnymi typami danych, których nie jesteś świadomy. Rozważ obiekty z przeciążonym operatorem nawiasów, aby zwrócić wartość całkowitą reprezentującą pole bitowe.
Joey Carson,

12

Jest to technika unikania pisania (zmienna! = 0) - tj. Konwertowania dowolnego typu na bool.

Taki kod IMO nie ma miejsca w systemach, które wymagają konserwacji - ponieważ nie jest to kod czytelny od razu (stąd pytanie na pierwszym miejscu).

Kod musi być czytelny - w przeciwnym razie zostawisz spuściznę długu czasu na przyszłość - ponieważ zrozumienie czegoś, co jest niepotrzebnie zagmatwane, wymaga czasu.


8
Moja definicja triku to coś, czego nie każdy może zrozumieć przy pierwszym czytaniu. Coś, co wymaga rozwiązania, to podstęp. Również okropne, ponieważ! operator może być przeciążony ...
Richard Harrison

6
@ orlandu63: proste rzutowanie na typ to bool(expr): robi to dobrze i wszyscy rozumieją intencje od pierwszego wejrzenia. !!(expr)jest podwójną negacją, która przypadkowo przekształca się w bool ... to nie jest proste.
Adrien Plisson

12

Tak, jest poprawne i nie, nie brakuje Ci czegoś. !!jest konwersją do bool. Zobacz to pytanie, aby uzyskać więcej dyskusji.


9

Pomija ostrzeżenie kompilatora. Spróbuj tego:

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

Linia „bar” generuje „wymuszającą wartość bool„ true ”lub„ false ”(ostrzeżenie dotyczące wydajności)” w MSVC ++, ale wiersz „baz” wymyka się dobrze.


1
Najczęściej spotykane w Windows API, która sama nie wie o tym boolrodzaju - wszystko jest zakodowany jako 0lub 1w sposób int.
Mark Ransom

4

Jest operatorem! przeciążony?
Jeśli nie, prawdopodobnie robią to, aby przekonwertować zmienną na bool bez generowania ostrzeżenia. To zdecydowanie nie jest standardowy sposób robienia rzeczy.


4

Deweloperzy Legacy C nie miał typ Boolean, więc często #define TRUE 1i #define FALSE 0, a następnie wykorzystywane dowolne typy danych numerycznych dla logicznych porównań. Teraz, gdy już mamy bool, wiele kompilatorów będzie emitować ostrzeżenia, gdy pewne typy przypisań i porównań zostaną wykonane przy użyciu kombinacji typów liczbowych i logicznych. Te dwa zastosowania ostatecznie zderzą się podczas pracy ze starszym kodem.

Aby obejść ten problem, niektórzy programiści używają następującej tożsamości logicznej: !num_valuezwraca bool trueif num_value == 0; falseInaczej. !!num_valuezwraca, bool falsejeśli num_value == 0; trueInaczej. Pojedyncza negacją jest wystarczający do przekształcenia num_valuena bool; Jednak podwójna negacja jest konieczna, aby przywrócić pierwotny sens wyrażenia boolowskiego.

Ten wzorzec jest znany jako idiom , czyli coś powszechnie używanego przez osoby zaznajomione z językiem. Dlatego nie uważam tego za anty-wzór, tak bardzo, jak bym chciał static_cast<bool>(num_value). Rzutowanie może bardzo dobrze dać poprawne wyniki, ale niektóre kompilatory emitują wtedy ostrzeżenie o wydajności, więc nadal musisz się tym zająć.

Innym sposobem rozwiązania tego problemu jest powiedzenie (num_value != FALSE). Z tym też !!num_valuenie przeszkadza , ale w sumie jest o wiele mniej rozwlekły, może być wyraźniejszy i nie wprowadza zamieszania, gdy zobaczysz go po raz drugi.


2

!! był używany do radzenia sobie z oryginalnym C ++, który nie miał typu boolowskiego (podobnie jak C).


Przykładowy problem:

Wewnątrz if(condition), na conditionpotrzeby oceny do pewnego rodzaju podobnego double, int, void*, etc., ale nie bool, ponieważ jeszcze nie istnieje.

Załóżmy, że istnieje klasa int256(256-bitowa liczba całkowita) i wszystkie konwersje / rzutowania na liczby całkowite zostały przeciążone.

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

Aby sprawdzić, czy xbyło „prawdziwe”, czy niezerowe, należy if (x)przekonwertować xna jakąś liczbę całkowitą, a następnie ocenić, czy jest intona niezerowa. Typowe przeciążenie (int) xzwróci tylko LSbitów x. if (x)testował wtedy tylko LSbity x.

Ale C ++ ma !operator. Przeciążony !xtyp zwykle oceniałby wszystkie bity x. Aby wrócić do logiki nieodwróconej, if (!!x)jest używana.

Ref Czy starsze wersje C ++ używały operatora „int” klasy podczas oceny warunku w instrukcji „if ()”?


1

Jak wspomniał Marcin , może mieć znaczenie, jeśli w grę wchodzi przeciążanie operatorów. W przeciwnym razie w C / C ++ nie ma to znaczenia, chyba że wykonujesz jedną z następujących czynności:

  • bezpośrednie porównanie do true(lub w C coś w rodzaju TRUEmakra), co jest prawie zawsze złym pomysłem. Na przykład:

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

  • po prostu chcesz, aby coś zostało przekonwertowane na ścisłą wartość 0/1. W C ++ przypisanie do a boolzrobi to niejawnie (dla tych rzeczy, które są niejawnie konwertowane na bool). W C lub jeśli masz do czynienia ze zmienną inną niż bool, jest to idiom, który widziałem, ale sam wolę tę (some_variable != 0)odmianę.

Myślę, że w kontekście większego wyrażenia boolowskiego po prostu zaśmieca to wszystko.


1

Jeśli zmienna jest typu obiektowego, może mieć! operator zdefiniowany, ale bez rzutowania na bool (lub, co gorsza, niejawne rzutowanie na int z inną semantyką. Dwukrotne wywołanie operatora! powoduje konwersję na bool, która działa nawet w dziwnych przypadkach.


0

Jest poprawne, ale w C, tutaj bez sensu - „if” i „&&” traktowałyby wyrażenie w ten sam sposób bez „!!”.

Przypuszczam, że powodem tego w C ++ jest to, że '&&' może być przeciążony. Ale z drugiej strony może „!”, Więc tak naprawdę nie gwarantuje uzyskania wartości bool bez patrzenia na kod typów variablei api.call. Może ktoś z większym doświadczeniem w C ++ mógłby to wyjaśnić; być może jest to środek dogłębnej obrony, a nie gwarancja.


Kompilator traktowałby te same wartości w obu przypadkach, gdyby był używany tylko jako operand dla iflub &&, ale użycie !!może pomóc w niektórych kompilatorach, jeśli if (!!(number & mask))zostanie zastąpione bit triggered = !!(number & mask); if (triggered); na niektórych wbudowanych kompilatorach z typami bitów przypisanie np. 256 do typu bitowego da zero. Bez !!pozornie bezpieczna transformacja (skopiowanie ifwarunku do zmiennej, a następnie rozgałęzienie) nie będzie bezpieczna.
supercat

0

Może programiści myśleli o czymś takim ...

!! myAnswer jest logiczna. W kontekście powinno to stać się logiczne, ale po prostu uwielbiam robić huk, aby się upewnić, ponieważ kiedyś pojawił się tajemniczy błąd, który mnie ugryzł i bang bang, zabiłem go.


0

Może to być przykład sztuczki z podwójnym uderzeniem , zobacz The Safe Bool Idiom, aby uzyskać więcej informacji. Tutaj podsumowuję pierwszą stronę artykułu.

W C ++ istnieje wiele sposobów na zapewnienie testów logicznych dla klas.

Oczywistym sposobem jest operator booloperator konwersji.

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

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

Możemy przetestować klasę,

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

Jednak opereator booljest uważany za niebezpieczny, ponieważ zezwala na bezsensowne operacje, takie jak test << 1;lub int i=test.

Używanie operator!jest bezpieczniejsze, ponieważ unikamy niejawnej konwersji lub problemów z przeciążeniem.

Wdrożenie jest banalne,

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

Dwa idiomatyczne sposoby testowania Testableobiektu to

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

Pierwsza wersja if (!!test)to coś, co niektórzy nazywają sztuczką z podwójnym hukiem .


1
Od C ++ 11 można użyć, explicit operator boolaby zapobiec niejawnej konwersji do innych typów całkowitych.
Arne Vogel
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.