Używanie operatorów bitowych dla wartości logicznych w C ++

84

Czy jest jakiś powód, aby nie używać operatorów bitowych &, | i ^ dla wartości „bool” w C ++?

Czasami napotykam sytuacje, w których chcę, aby dokładnie jeden z dwóch warunków był prawdziwy (XOR), więc po prostu wrzucam operator ^ do wyrażenia warunkowego. Czasami chcę również, aby wszystkie części warunku były oceniane, czy wynik jest prawdziwy, czy nie (zamiast tworzyć zwarcie), więc używam & i |. Muszę też czasami gromadzić wartości logiczne, a & = i | = mogą być całkiem przydatne.

Robiąc to, dostałem kilka uniesionych brwi, ale kod jest nadal znaczący i czystszy niż byłby w innym przypadku. Czy jest jakiś powód, aby NIE używać ich do bools? Czy są jakieś nowoczesne kompilatory, które dają złe wyniki?

Jay Conrod
źródło
Jeśli od czasu zadania tego pytania nauczyłeś się wystarczająco dużo o C ++, powinieneś wrócić i cofnąć akceptację aktualnej odpowiedzi i zaakceptować odpowiedź Patricka Johnmeyera, aby poprawnie wyjaśnić różnicę w spięciach.
codetaku
Uwaga ogólna, ponieważ ludzie pytają, czy to dobry pomysł. Jeśli zależy Ci na pokryciu gałęzi testów jednostkowych, pożądane jest zmniejszenie oddziałów. Przydają się do tego operatory bitowe.
qznc

Odpowiedzi:

61

||i &&są operatorami logicznymi, a wbudowane mają gwarancję zwrócenia albo truelub false. Nic więcej.

|, &I ^są bitowe operatorów. Kiedy domena liczb, na których operujesz, wynosi tylko 1 i 0, to są one dokładnie takie same, ale w przypadkach, gdy twoje wartości logiczne nie są ściśle 1 i 0 - jak w przypadku języka C - możesz skończyć z pewnym zachowaniem nie chciałeś. Na przykład:

BOOL two = 2;
BOOL one = 1;
BOOL and = two & one;   //and = 0
BOOL cand = two && one; //cand = 1

Jednak w C ++ booltyp jest gwarantowany tylko jako a truelub a false(które konwertują niejawnie na odpowiednio 1i 0), więc takie podejście jest mniejszym zmartwieniem, ale fakt, że ludzie nie są przyzwyczajeni do oglądania takich rzeczy w kodzie to dobry argument, żeby tego nie robić. Po prostu powiedz b = b && xi skończ z tym.

Patrick
źródło
nie ma czegoś takiego jak logiczny operator xor ^^ w C ++. Dla obu argumentów jako bools! = Robi to samo, ale może być niebezpieczne, jeśli którykolwiek z argumentów nie jest wartością logiczną (tak jak w przypadku użycia ^ dla logicznego xor). Nie jestem pewien, jak to zostało zaakceptowane ...
Greg Rogers,
1
Z perspektywy czasu to było głupie. W każdym razie, zmieniono przykład na &&, aby rozwiązać problem. Przysięgam, że kiedyś w przeszłości używałem ^^, ale to chyba nie miało miejsca.
Patrick,
C ma typ bool, który może wynosić 0 lub 1 od 10 lat. _Bool
Johannes Schaub - litb
16
Są wciąż nie to samo, nawet dla bools ponieważ nie krótkie circut
aaronman
4
Tak, zdecydowanie uważam, że nie powinno to być akceptowaną odpowiedzią, dopóki nie zmienisz „Kiedy domena liczb, na których operujesz, to tylko 1 i 0, to są one dokładnie takie same” na „Kiedy domena liczb, na których operujesz, to tylko 1 i 0, jedyną różnicą jest to, że operatory bitowe nie powodują zwarć ”. Pierwsze stwierdzenie jest zdecydowanie błędne, więc w międzyczasie odrzuciłem tę odpowiedź, ponieważ zawiera to zdanie.
codetaku
32

Z dwóch głównych powodów. Krótko mówiąc, zastanów się dokładnie; może być dobry powód, ale jeśli w twoich komentarzach jest BARDZO wyraźny, ponieważ może być kruchy i, jak sam mówisz, ludzie nie są przyzwyczajeni do takiego kodu.

Bitwise xor! = Logical xor (z wyjątkiem 0 i 1)

Po pierwsze, jeśli operujesz na wartościach innych niż falsei true(lub 0i 1jako liczby całkowite), ^operator może wprowadzić zachowanie, które nie jest równoważne z logicznym xor. Na przykład:

int one = 1;
int two = 2;

// bitwise xor
if (one ^ two)
{
  // executes because expression = 3 and any non-zero integer evaluates to true
}

// logical xor; more correctly would be coded as
//   if (bool(one) != bool(two))
// but spelled out to be explicit in the context of the problem
if ((one && !two) || (!one && two))
{
  // does not execute b/c expression = ((true && false) || (false && true))
  // which evaluates to false
}

Podziękowania dla użytkownika @Patrick za wyrażenie tego jako pierwszego.

Kolejność operacji

Po drugie |, &i ^, jak operatorzy bitowe, nie zrobić zwarcia. Ponadto wiele operatorów bitowych połączonych ze sobą w jednej instrukcji - nawet z jawnymi nawiasami - można zmienić w kolejności poprzez optymalizację kompilatorów, ponieważ wszystkie 3 operacje są zwykle przemienne. Jest to ważne, jeśli liczy się kolejność operacji.

Innymi słowy

bool result = true;
result = result && a() && b();
// will not call a() if result false, will not call b() if result or a() false

nie zawsze da ten sam wynik (lub stan końcowy) jak

bool result = true;
result &= (a() & b());
// a() and b() both will be called, but not necessarily in that order in an
// optimizing compiler

Jest to szczególnie ważne, ponieważ możesz nie kontrolować metod a()i b(), lub ktoś inny może przyjść i zmienić je później, nie rozumiejąc zależności, i spowodować nieprzyjemny (i często tylko do wydania) błąd.

Patrick Johnmeyer
źródło
Niezbyt uczciwe porównanie, jeśli rzucasz do bool w jednym przypadku, a nie w drugim ... Cast to bool w obu przypadkach jest tym, co sprawi, że to zadziała i dlaczego jest kruche (ponieważ musisz o tym pamiętać).
Greg Rogers
Wyjaśnienie zwarcia jest powodem, dla którego powinno to być absolutnie akceptowaną odpowiedzią; Odpowiedź Patricka (eee ... inny Patryk, który właśnie nazywa się Patrick) jest całkowicie błędna, mówiąc: „Kiedy domena liczb, na których
operujesz,
13

Myślę

a != b

jest tym, czego chcesz

Mark Borgerding
źródło
1
To prawda, +1. Ale nie ma przypisanej wersji tego operatora (że !==tak powiem), więc jeśli obliczasz XOR sekwencji boolwartości, musisz zapisać acc = acc!= condition(i);w treści pętli. Kompilator prawdopodobnie poradzi sobie z tym tak wydajnie, jakby !==istniał, ale niektórzy mogą uznać, że nie wygląda to ładnie i wolą alternatywę polegającą na dodaniu wartości logicznych jako liczb całkowitych, a następnie przetestowaniu, czy suma jest nieparzysta.
Marc van Leeuwen
10

Uniesione brwi powinny powiedzieć ci wystarczająco dużo, aby przestać to robić. Nie piszesz kodu dla kompilatora, piszesz go najpierw dla innych programistów, a potem dla kompilatora. Nawet jeśli kompilatory działają, zaskakiwanie innych ludzi nie jest tym, czego chcesz - operatory bitowe służą do operacji bitowych, a nie do bools.
Przypuszczam, że jesz też jabłka widelcem? Działa, ale zaskakuje ludzi, więc lepiej tego nie robić.

kokos
źródło
3
Tak, to powinna być najlepsza odpowiedź. Postępuj zgodnie z zasadą najmniejszego zaskoczenia. Kod, który wykonuje niezwykłe rzeczy, jest trudniejszy do odczytania i zrozumienia, a kod jest czytany znacznie częściej niż jest napisany. Nie używaj takich fajnych sztuczek.
Eloff
1
Jestem tutaj, ponieważ mój kolega użył operatora bitowego „i” dla bool. A teraz spędzam czas próbując zrozumieć, czy to prawda, czy nie. Gdyby tego nie zrobił, zrobiłbym teraz coś bardziej użytecznego -_-. Jeśli cenisz czas swoich kolegów, PROSZĘ nie używaj operatorów bitowych dla bool!
anton_rh
9

Wady operatorów poziomów bitowych.

Ty pytasz:

„Czy jest jakiś powód, aby nie używać operatory bitowe &, |i ^dla«bool»wartości w C ++? ”

Tak, operatory logiczne , że jest wbudowany w wysokiego szczebla operatorów logicznych !, &&a ||oferują następujące zalety:

  • Gwarantowana konwersji argumentów do bool, czyli 0i 1wartości porządkowej.

  • Gwarantowana ocena zwarcia, w której ocena wyrażenia zatrzymuje się, gdy tylko znany jest wynik końcowy.
    Można to zinterpretować jako logikę wartości drzewa, z True , False i Indeterminate .

  • Czytelne odpowiedniki tekstowe not, anda ornawet jeśli nie będę z nich korzystać siebie.
    Jako czytelnik antymonu zauważa w komentarzu też bitlevel operatorzy mają alternatywne znaki, a mianowicie bitand, bitor, xori compl, ale moim zdaniem są mniej czytelne niż and, ori not.

Mówiąc najprościej, każda taka zaleta operatorów wysokiego poziomu jest wadą operatorów bitlevel.

W szczególności, ponieważ operatory bitowe nie mają konwersji argumentów na 0/1, otrzymasz np. 1 & 20, while 1 && 2true. Ponadto ^bitowe wyłączne lub, mogą działać nieprawidłowo w ten sposób. Uważane za wartości logiczne 1 i 2 są takie same, mianowicie true, ale traktowane jako wzorce bitów są różne.


Jak wyrazić logikę albo / lub w C ++.

Następnie podajesz trochę tła dla pytania,

„Czasami napotykam sytuacje, w których chcę, aby dokładnie jeden z dwóch warunków był prawdziwy (XOR), więc po prostu wrzucam operator ^ do wyrażenia warunkowego”.

Cóż, operatory bitowe mają wyższy priorytet niż operatory logiczne. Oznacza to w szczególności, że w wyrażeniu mieszanym, takim jak

a && b ^ c

otrzymujesz być może nieoczekiwany wynik a && (b ^ c).

Zamiast tego pisz po prostu

(a && b) != c

wyrażając bardziej zwięźle, co masz na myśli.

Dla wielu argumentów albo / albo nie ma operatora C ++, który wykonuje to zadanie. Na przykład, jeśli napiszesz a ^ b ^ c, to nie jest to wyrażenie, które mówi „albo a, balbo cjest prawdziwe”. Zamiast tego mówi, „nieparzysta liczba a, bi csą prawdziwe”, co może być 1 z nich lub wszystkich 3 ...

Aby wyrazić ogólne „albo / lub kiedy” a, bi csą typu bool, po prostu napisz

(a + b + c) == 1

lub, jeśli nie są boolargumentami, zamień je na bool:

(!!a + !!b + !!c) == 1


Używanie &=do gromadzenia wyników boolowskich.

Ty dalej rozwijasz,

„Ja też trzeba gromadzić wartości logiczne czasami, a &=i |=?mogą być bardzo użyteczne.”

Dobrze, odpowiada to sprawdzenie, czy odpowiednio wszystkie lub którykolwiek warunek jest spełniony, a de prawem Morgan mówi, jak przejść z jednego do drugiego. Oznacza to, że potrzebujesz tylko jednego z nich. Zasadniczo można by użyć *=jako &&=operatora (jak odkrył stary dobry George Boole, logiczne ORAZ można bardzo łatwo wyrazić jako mnożenie), ale myślę, że to zdziwiłoby i być może wprowadziłoby w błąd opiekunów kodu.

Weź również pod uwagę:

struct Bool
{
    bool    value;

    void operator&=( bool const v ) { value = value && v; }
    operator bool() const { return value; }
};

#include <iostream>

int main()
{
    using namespace std;

    Bool a  = {true};
    a &= true || false;
    a &= 1234;
    cout << boolalpha << a << endl;

    bool b = {true};
    b &= true || false;
    b &= 1234;
    cout << boolalpha << b << endl;
}

Wyjście z Visual C ++ 11.0 i g ++ 4.7.1:

prawdziwe
fałszywy

Przyczyną różnicy w wynikach jest to, że poziom bitów &=nie zapewnia konwersji na boolargument po prawej stronie.

Które z tych wyników chcesz wykorzystać &=?

Jeśli to pierwsze, truelepiej zdefiniuj operator (np. Jak wyżej) lub nazwaną funkcję, albo użyj jawnej konwersji wyrażenia po prawej stronie, albo napisz aktualizację w całości.

Pozdrawiam i hth. - Alf
źródło
operatory bitowe mają również odpowiedniki tekstowe
Antimony
@Antimony: Nie rozumiałem, że komentarz w pierwszym, ale tak, operacje bitlevel mieć alternatywne znaki bitand, bitor, xori compl. Myślę, że dlatego użyłem określenia „czytelny”. Oczywiście czytelność np. compl 42Jest subiektywna. ;-)
Pozdrawiam i hth. - Alf
O ile wiem, operatory logiczne mogą być przeładowane innymi typami argumentów (chociaż zazwyczaj nie są), więc pierwszy punkt „gwarantowana konwersja argumentów na bool” jest tylko konsekwencją konwencji, a nie gwarancją, którą język C ++ faktycznie daje ty.
Marc van Leeuwen
@MarcvanLeeuwen: Prawdopodobnie twój podsystem wizualny nie zauważył „wbudowanych operatorów logicznych wysokiego poziomu!, && i ||”. Masz rację, że operatory te mogą być przeciążone, a następnie głównym problemem jest to, że semantyka zmienia się subtelnie, nie jest już zwarciem w ocenie. Ale to jest problem z przeciążeniami, a nie z wbudowanymi operatorami.
Pozdrawiam i hth. - Alf
Zgadza się (przegapiłem to). Ale to stwierdzenie nadal jest mylące, ponieważ po prostu mówisz, że te operatory przyjmują argumenty boolowskie; jeśli (i tylko jeśli) zostaną wybrane wbudowane wersje operatorów, kompilator wstawi konwersję argumentów. Podobnie wbudowany (wysokiego poziomu) operator dodawania liczb całkowitych bez znaku +gwarantuje konwersję swoich argumentów na unsigned, ale to nie zapobiega unsigned int n=-1; n+=3.14;faktycznemu użyciu operacji dodawania double(czy jest to float?). (Porównaj swój &=przykład.)
Marc van Leeuwen
3

W przeciwieństwie do odpowiedzi Patricka, C ++ nie ma ^^operatora do wykonywania wyłącznego zwarcia lub. Jeśli pomyślisz o tym przez chwilę, posiadanie ^^operatora i tak nie miałoby sensu: w przypadku wyłącznego lub wynik zawsze zależy od obu operandów. Jednak ostrzeżenie Patricka dotyczące booltypów innych niż „boolowskie” sprawdza się równie dobrze w porównaniu 1 & 2z 1 && 2. Jednym z klasycznych przykładów jest GetMessage()funkcja systemu Windows , która zwraca stan trójstanowy BOOL: niezerowy 0, lub -1.

Używanie &zamiast &&i |zamiast ||nie jest rzadką literówką, więc jeśli robisz to celowo, zasługuje na komentarz wyjaśniający dlaczego.

bk1e
źródło
6
ZA ^^Operator będzie nadal użyteczny niezależnie od rozpatrzenia zwarcia. To by a) oceniało operandy w kontekście boolowskim ib) gwarantowało zwrócenie 1 lub 0.
Craig McQueen
@CraigMcQueen. Łatwo jest zdefiniować inline bool XOR(bool a,bool b) { return a!=b; }i uzyskać to, czego chcesz, z wyjątkiem tego, że jest to funkcja, a nie operator (wrostek). Możesz też bezpośrednio użyć !=lub przeciążać innego operatora o tym znaczeniu, ale wtedy oczywiście musisz bardzo uważać, aby przypadkowo nie użyć niezamierzonego przeciążenia o tej samej nazwie. I nawiasem mówiąc ||i &&wróć truelub falsenie 1lub 0.
Marc van Leeuwen
I wydaje się, że fakt !,|| i &&ocenić swoje argumenty w kontekście logicznym jest tylko dlatego, że te podmioty są rzadko przeciążony przyjąć inne rodzaje; o ile wiem, język dopuszcza takie przeciążenia, a zatem nie gwarantuje oceny argumentów w kontekście boolowskim.
Marc van Leeuwen
2

Patrick zrobił słuszne uwagi i nie zamierzam ich powtarzać. Mogę jednak zasugerować ograniczenie instrukcji `` if '' do czytelnego angielskiego wszędzie tam, gdzie to możliwe, używając dobrze nazwanych zmiennych boolowskich.Na przykład, i jest to użycie operatorów logicznych, ale możesz równie dobrze użyć bitowych i odpowiednio nazwać boole:

bool onlyAIsTrue = (a && !b); // you could use bitwise XOR here
bool onlyBIsTrue = (b && !a); // and not need this second line
if (onlyAIsTrue || onlyBIsTrue)
{
 .. stuff ..
}

Możesz pomyśleć, że użycie wartości logicznej wydaje się niepotrzebne, ale pomaga w dwóch głównych rzeczach:

  • Twój kod jest łatwiejszy do zrozumienia, ponieważ pośrednia wartość logiczna warunku „jeśli” sprawia, że ​​intencja warunku jest bardziej wyraźna.
  • Jeśli używasz niestandardowego lub nieoczekiwanego kodu, takiego jak operatory bitowe na wartościach logicznych, ludzie mogą znacznie łatwiej zrozumieć, dlaczego to zrobiłeś.

EDYCJA: Nie powiedziałeś wyraźnie, że chciałeś warunków warunkowych dla stwierdzeń „jeśli” (chociaż wydaje się to najbardziej prawdopodobne), takie było moje założenie. Ale moja sugestia dotycząca pośredniej wartości logicznej jest nadal aktualna.

genix
źródło
1

Korzystanie z operacji bitowych dla bool pomaga zaoszczędzić niepotrzebną logikę przewidywania rozgałęzień przez procesor, wynikającą z instrukcji „cmp” wprowadzonej przez operacje logiczne.

Zastąpienie operacji logicznych operacjami bitowymi (gdzie wszystkie operandy są typu bool) generuje bardziej wydajny kod oferujący ten sam wynik. W idealnym przypadku wydajność powinna przeważać nad wszystkimi korzyściami zwarciowymi, które można wykorzystać przy zamawianiu przy użyciu operacji logicznych.

Może to spowodować, że kod będzie nieco nieczytelny, aczkolwiek programista powinien skomentować go, podając powody, dla których tak się stało.

Anoop Menon
źródło
0

IIRC, wiele kompilatorów C ++ będzie ostrzegać podczas próby rzutowania wyniku operacji bitowej jako bool. Musiałbyś użyć rzutowania typu, aby kompilator był zadowolony.

Użycie operacji bitowej w wyrażeniu if mogłoby służyć tej samej krytyce, chociaż być może nie przez kompilator. Każda wartość niezerowa jest uznawana za prawdę, więc coś w rodzaju „if (7 i 3)” będzie prawdziwe. Takie zachowanie może być akceptowalne w Perlu, ale C / C ++ to bardzo wyraźne języki. Myślę, że brwi Spocka to należyta staranność. :) Dodałbym „== 0” lub „! = 0”, aby było doskonale jasne, jaki był Twój cel.

Ale w każdym razie brzmi to jak osobiste preferencje. Przepuściłbym kod za pomocą linta lub podobnego narzędzia i sprawdziłbym, czy również uważa, że ​​to nierozsądna strategia. Osobiście brzmi to jak błąd w kodowaniu.

spoulson
źródło
Twój post zmotywował mnie do przetestowania tego, a gcc nie ostrzegł! Bummer, ponieważ zamierzałem użyć tego ostrzeżenia, aby uzasadnić pozwolenie mi na uruchomienie & = w celu zebrania wyników bool, ufając, że inni zobaczą ostrzeżenie, jeśli później zmienią mój test. Mój kod zawiódłby nawet dla liczb całkowitych, ponieważ dają one 0 / fałsz - bez ostrzeżeń! Czas na refaktoryzację ...
sage