Почему в С ++ нет && = или || = для логических значений? - PullRequest
108 голосов
/ 21 марта 2010

Есть ли "очень плохая вещь" , которая может случиться, && = и || = использовались как синтаксический сахар для bool foo = foo && bar и bool foo = foo || bar?

Ответы [ 3 ]

65 голосов
/ 21 марта 2010

A bool может быть только true или false в C ++. Таким образом, использование &= и |= относительно безопасно (хотя мне не особо нравятся нотации). Правда, они будут выполнять битовые операции, а не логические операции (и, следовательно, они не будут закорачиваться), но эти битовые операции следуют за четко определенным отображением, которое фактически эквивалентно логическим операциям, до тех пор, пока как оба операнда имеют тип bool. 1

Вопреки тому, что здесь говорили другие люди, bool в C ++ никогда не должен иметь другого значения, такого как 2. При присвоении этого значения bool оно будет преобразовано в true согласно стандарту.

Единственный способ получить недопустимое значение в bool - использовать reinterpret_cast для указателей:

int i = 2;
bool b = *reinterpret_cast<bool*>(&i);
b |= true; // MAY yield 3 (but doesn’t on my PC!)

Но так как этот код в любом случае приводит к неопределенному поведению, мы можем спокойно игнорировать эту потенциальную проблему при согласовании кода C ++.


1 По общему признанию, это довольно большое предостережение, как иллюстрирует комментарий Анджью:

bool b = true;
b &= 2; // yields `false`.

Причина в том, что b & 2 выполняет целочисленное продвижение, так что выражение тогда эквивалентно static_cast<int>(b) & 2, что приводит к 0, который затем преобразуется обратно в bool. Поэтому верно, что наличие operator &&= улучшило бы безопасность типов.

42 голосов
/ 21 марта 2010

&& и & имеют различную семантику: && не будет оценивать второй операнд, если первый операнд равен false. то есть что-то вроде

flag = (ptr != NULL) && (ptr->member > 3);

безопасно, но

flag = (ptr != NULL) & (ptr->member > 3);

- нет, хотя оба операнда имеют тип bool.

То же самое верно для &= и |=:

flag = CheckFileExists();
flag = flag && CheckFileReadable();
flag = flag && CheckFileContents();

будет вести себя иначе, чем:

flag = CheckFileExists();
flag &= CheckFileReadable();
flag &= CheckFileContents();
22 голосов
/ 29 марта 2012

Краткий ответ

Все операторы +=, -=, *=, /=, &=, |= ... являются арифметическими и дают одинаковое ожидание:

x &= foo()  // We expect foo() be called whatever the value of x

Однако операторы &&= и ||= будут логичными, и эти операторы могут быть подвержены ошибкам, поскольку многие разработчики ожидают, что foo() всегда вызывается в x &&= foo().

bool x;
// ...
x &&= foo();           // Many developers might be confused
x = x && foo();        // Still confusing but correct
x = x ? foo() : x;     // Understandable
x = x ? foo() : false; // Understandable
if (x) x = foo();      // Obvious
  • Нам действительно нужно сделать C / C ++ еще более сложным, чтобы получить ярлык для x = x && foo()?

  • Неужели мы действительно хотим больше запутать загадочное утверждение x = x && foo()?
    Или мы хотим написать значимый код вроде if (x) x = foo();?


Длинный ответ

Пример для &&=

Если оператор &&= был доступен, то этот код:

bool ok = true; //becomes false when at least a function returns false
ok &&= f1();
ok &&= f2(); //we may expect f2() is called whatever the f1() returned value

эквивалентно:

bool ok = true;
if (ok) ok = f1();
if (ok) ok = f2(); //f2() is called only when f1() returns true

Этот первый код подвержен ошибкам , потому что многие разработчики думают, что f2() всегда вызывается независимо от возвращаемого значения f1(). Это похоже на запись bool ok = f1() && f2();, где f2() вызывается только тогда, когда f1() возвращает true.

  • Если разработчик действительно хочет, чтобы f2() вызывался только тогда, когда f1() возвращает true, следовательно, второй приведенный выше код менее подвержен ошибкам.
  • Иначе (разработчик хочет, чтобы f2() всегда вызывался), &= достаточно:

Пример для &=

bool ok = true;
ok &= f1();
ok &= f2(); //f2() always called whatever the f1() returned value

Более того, компилятору проще оптимизировать приведенный выше код, чем приведенный ниже:

bool ok = true;
if (!f1())  ok = false;
if (!f2())  ok = false;  //f2() always called

Сравните && и &

Мы можем задаться вопросом, дают ли операторы && и & одинаковый результат при применении к значениям bool?

Давайте проверим, используя следующий код C ++:

#include <iostream>

void test (int testnumber, bool a, bool b)
{
   std::cout << testnumber <<") a="<< a <<" and b="<< b <<"\n"
                "a && b = "<< (a && b)  <<"\n"
                "a &  b = "<< (a &  b)  <<"\n"
                "======================"  "\n";
}

int main ()
{
    test (1, true,  true);
    test (2, true,  false);
    test (3, false, false);
    test (4, false, true);
}

Выход:

1) a=1 and b=1
a && b = 1
a &  b = 1
======================
2) a=1 and b=0
a && b = 0
a &  b = 0
======================
3) a=0 and b=0
a && b = 0
a &  b = 0
======================
4) a=0 and b=1
a && b = 0
a &  b = 0
======================

Заключение

Поэтому ДА мы можем заменить && на & для bool значений ;-)
Так что лучше используйте &= вместо &&=.
Мы можем считать &&= бесполезным для логических значений.

То же самое для ||=

оператор |= также меньше подвержен ошибкам , чем ||=

Если разработчик хочет, чтобы f2() вызывался только тогда, когда f1() возвращает false вместо:

bool ok = false;
ok ||= f1();
ok ||= f2(); //f2() is called only when f1() returns false
ok ||= f3(); //f3() is called only when f1() or f2() return false
ok ||= f4(); //f4() is called only when ...

Я советую следующую более понятную альтернативу:

bool ok = false;
if (!ok) ok = f1();
if (!ok) ok = f2();
if (!ok) ok = f3();
if (!ok) ok = f4();
// no comment required here (code is enough understandable)

или, если вы предпочитаете все в одну строку стиль:

// this comment is required to explain to developers that 
// f2() is called only when f1() returns false, and so on...
bool ok = f1() || f2() || f3() || f4();
...