为什么 c + + 对布尔型没有 & & = 或 | | = ?

是否有一个 “非常糟糕的事情”可以发生 &&=||=被用作 bool foo = foo && barbool foo = foo || bar的句法糖?

55666 次浏览

在 C + + 中,bool可能只是 truefalse。因此,使用 &=|=是相对安全的(尽管我不是特别喜欢这个符号)。的确,它们将执行位操作而不是逻辑操作(因此它们不会短路) ,但是这些位操作遵循定义良好的映射,这实际上等同于逻辑操作 只要 < em > 两个操作数都是 bool类型的.1

与其他人在这里所说的相反,C + + 中的 bool绝不能有像 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 + + 代码的问题。


诚然,这是一个相当大的警告,正如安格鲁的评论所说明的那样:

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

原因是 b & 2执行整数提升,这样表达式就等效于 static_cast<int>(b) & 2,从而产生 0,然后将其转换回 bool。因此,operator &&=的存在确实会提高类型安全性。

&&&有不同的语义: 如果第一个操作数是 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();

长话短说

所有运算符 +=-=*=/=&=|=... 都是算术运算,并提供相同的期望:

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

第一个代码是 容易出错,因为许多开发人员认为无论 f1()返回的值是什么,f2()总是被称为。这就像编写 bool ok = f1() && f2();一样,只有当 f1()返回 true时才调用 f2()

  • 如果开发人员实际上只希望在 f1()返回 true时才调用 f2(),那么上面的第二个代码就不太容易出错。
  • 否则(开发人员希望始终调用 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值,我们可以用 &代替 &&; -)
因此,最好使用 &=而不是 &&=
我们可以认为 &&=对布尔型无用。

||=也是

操作者 |=也小于 容易出错||=

如果开发人员希望仅在 f1()返回 false时才调用 f2(),而不是:

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();