为什么C++没有用于布尔类型的&&=或||=运算符?

155

如果将 &&=||= 用作 bool foo = foo && barbool foo = foo || bar 的语法糖,会不会发生"非常糟糕的事情"


6
请看这个问题:https://dev59.com/H3E95IYBdhLWcg3wadKq 虽然那个问题是关于Java的,但由于C语言有相同的起源,大部分的论点也是适用的。 - jamesdlin
基本上,只是因为C++没有它,因为他们没有把它放进去 - 像Ruby这样的语言有它。唉... - Kache
2
但在Ruby中,x ||= y不是对于任何类型都等价于C++的x = x ? x : y;吗?换句话说,“如果尚未设置,则设置为y”。这比C或C++的x ||= y要有用得多,后者(除非进行运算符重载)将执行“将x设置为(bool)y,除非已经设置”。我不急于添加另一个运算符,这似乎有点无力。只需编写if (!x) x = (bool)y即可。但是,我并没有真正使用足够的bool变量来希望具有仅与该一种类型真正有用的额外运算符。 - Steve Jessop
2
我相信C++没有&&=||=的主要原因只是因为C语言没有这些运算符。我相当确定C语言没有这些运算符的原因是因为这种功能被认为不够有益。 - Jonathan Leffler
3
此外,对于极端追求严谨的人来说,符号 bool foo = foo || bar; 会引发未定义行为,因为在评估 foo || bar 之前没有初始化 foo。当然,这是想要表达的意思类似于 bool foo = …initialization…; …; foo = foo || bar;,那么问题就是合法的。 - Jonathan Leffler
3个回答

99
在C++中,布尔类型只能是truefalse。因此,使用&=|=操作符相对安全(尽管我不太喜欢这种表示方法)。虽然它们执行的是位运算而不是逻辑运算(因此它们不会短路),但是只要两个操作数都是bool类型,这些位运算将遵循明确定义的映射,与逻辑运算等效。1 与其他人所说的相反,在C++中,bool绝不能有不同于truefalse的值,如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诚然,正如Angew的评论所示,这是一个相当大的警告:

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

原因是b & 2会进行整数提升,使得表达式等价于static_cast<int>(b) & 2,这将导致结果为0,然后再转换回bool类型。因此,存在一个operator &&=将会提高类型安全性。

4
但是,"&&"和"||"运算符适用于任何可以转换为bool的类型,而不仅仅是bool类型。 - dan04
14
即使在 bool 类型上,它们也并不执行相同的操作。||&& 会进行短路运算,也就是说,如果第一个操作数为 true&& 的情况下则为 false),那么第二个操作数将不再被执行。而 |&|=&= 则会始终对两个操作数进行求值。 - Niki
5
这并没有回答为什么 &&= 和 ||= 不是 C++ 运算符的问题。 - thang
12
对于类型为bool的左操作数来说,使用&=运算符是不安全的,因为右操作数可能是除了bool以外的其他类型(例如返回非零值表示真的islower或另一个C标准库函数)。如果我们有假设性的&&=运算符,它可能会强制将右操作数转换为bool,而&=则不会这样做。换句话说,bool b = true; b &= 2;的结果是b == false - Angew is no longer proud of SO
2
@Antonio 人群很严格。 - Konrad Rudolph
显示剩余11条评论

51

&&&具有不同的语义:&&如果第一个操作数为false,则不会计算第二个操作数。 比如:

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

是安全的,但是

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

尽管两个操作数都是bool类型,但is not运算符并不适用。

&=|=同样如此:

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

会表现出不同的行为:

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

49
在我看来,有更多的理由使用 &&=。=P - Kache
6
这其实并不是一个答案。 - Catskul

36

简短回答

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

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

然而,运算符&&=||=是逻辑上的,这些运算符可能会出现错误,因为许多开发人员会期望x &&= foo()中始终调用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();一样,在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();

15
如果我真的想要这种行为怎么办?也就是说,如果左边的表达式出现错误,右边的表达式不会被执行。写两次变量很麻烦,例如 success = success && DoImportantStuff() - Niklas R
1
我的建议是编写 if(success) success = DoImportantStuff()。如果允许语句 success &&= DoImportantStuff(),许多开发人员会认为无论 success 的值如何,DoImportantStuff() 总是会被调用。希望这回答了您的疑问...我也改善了许多我的回答部分。请告诉我现在我的回答是否更容易理解?祝好,再见 ;-) - oHo
12
如果允许使用语句 success &&= DoImportantStuff(),很多开发者会认为无论 success 是什么值,DoImportantStuff() 都会被调用。但是,如果他们记得 if 语句的逻辑,对于 &&= 应该没有问题。 - pilkch
3
我不明白为什么有些人会认为f1()总是在 ok &&= f(1) 中被执行,而不认为它总是在 ok = ok && f(1) 中被执行。我认为这两种情况同样可能发生。 - einpoklum
2
我实际上期望 v1 += e2 是变量 v1 和表达式 e2 的语法糖等效于 v1 = v1 + e1。只是一种简写符号,仅此而已。 - einpoklum
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接