【问题标题】:Is it guaranteed that bitwise "and" for bool does not short circuit?是否保证 bool 的按位“和”不会短路?
【发布时间】:2019-06-03 05:47:35
【问题描述】:

我最近遇到了一个代码 sn-p,看起来像这样:

bool MyClass::do_work()
{
    bool success = true;
    for (auto const& worker : m_workers)
    {
        success &= worker.do_work();  // worker.do_work() returns a bool
    }
    return success;
}

如果我理解正确,如果所有工作人员返回 true,则该函数返回 true,如果任何工作人员返回 false,则返回 false。但是,它总是评估所有工作人员(这是所需的)。不涉及短路评估,因为使用了位运算符&=,而不是逻辑运算符&&

这种行为是否得到保证?准确地说,是否保证按位& 总是计算两个操作数,即使它们是bool 类型?我遇到了许多关于 && 的保证短路评估的 SO 答案,但没有一个声明保证 & 的非短路评估。

如果这种行为得到保证,这是一种好的编程风格吗?看了一眼就明白了这个功能,因为我之前没见过这种风格,一开始我很困惑是否涉及短路评估。

还有比以下更好的选择吗?

bool MyClass::do_work()
{
    bool success = true;
    for (auto const& worker : m_workers)
    {
        if (!worker.do_work())
        {
            success = false;
        }
    }
    return success;
}

【问题讨论】:

  • 我会说这是一种非常令人困惑的风格:似乎很容易忽视无短路效应。
  • 运算符的所有操作数通常都会被求值,&&||?: 运算符是例外。该标准甚至提到&& “与& 不同,&& 保证从左到右的评估:如果第一个操作数是false,则不会评估第二个操作数。”。至于这是否是好的编程风格,这是基于意见的。
  • @You 我认为标准不能保证all_of 不会发生短路,那真的很奇怪。
  • “有没有更好的选择 [...]?” Some 会争辩,是的(?):std::count_if(m_workers.begin(), m_workers.end(), [](const auto& worker) { return !worker.do_work(); }) == 0;
  • 或者success = worker.do_work() && success;,如果你想坚持使用命令式循环。

标签: c++ c++11 coding-style language-lawyer


【解决方案1】:

除非标准明确规定,否则在 C++ 中,运算符的所有操作数都会被求值和不排序1

[intro.execution]

除非另有说明,对单个运算符的操作数和单个表达式的子表达式的求值是无序的。 [...] 运算符的操作数的值计算在运算符结果的值计算之前排序。 [...]

我想到的唯一三个例外是 &&||?: 运算符2

标准甚至提到&&3

[expr.log.and]

& 不同,&& 保证从左到右的评估:如果第一个操作数是 false,则不评估第二个操作数。

至于这是否是好的编程风格,这是基于意见的。


1 Unsequenced 基本上意味着如果您有A @ B(其中@ 是一个运算符),则可以在A 之前评估B(及其副作用) ,这就是为什么构造如i++ + ++i are undefined behavior

2 请注意,对于重载的&&|| 运算符,这不再是真的,因为这两个操作数都被计算了。 ?: 不能重载。

3|[expr.log.or] 中有类似的注释。

【讨论】:

  • & 不同,&& 保证从左到右的评估:如果第一个操作数是 false,则不评估第二个操作数。。让我换个说法:& 不保证从左到右的评估。 不幸的是,这个声明意味着& 操作符可能会执行短路评估,只是不能保证。跨度>
  • @pschill 不,这意味着& 可以像大多数运算符一样先计算右操作数,然后再计算左操作数。当您执行A op B 时,B 可以在A 之前进行评估,但两者都会被评估,&&||?: 除外。
  • @pschill 如果您愿意,|| 版本会更好一些:"不像 |,|| 保证从左到右的评估;此外 ,如果第一个操作数的计算结果为真,则不计算第二个操作数。"
  • @pschill 我已经编辑了我的答案以添加关于运算符默认行为的标准引用,该行为指出除非另有说明,否则计算运算符的所有操作数。
  • 好的,重点是运算符(&& 等除外)的计算方式与“普通”C++ 函数一样,这意味着所有参数都已计算。谢谢你:)
猜你喜欢
  • 2017-05-21
  • 2010-10-05
  • 2016-10-29
  • 2013-10-18
  • 1970-01-01
  • 2011-08-30
  • 1970-01-01
  • 2012-03-05
  • 2017-02-03
相关资源
最近更新 更多