我可以一次检查一小群布尔值吗?

Can I check a small array of bools in one go?

提问人:Zebrafish 提问时间:8/18/2019 最后编辑:SomeRandomDevZebrafish 更新时间:8/23/2019 访问量:3174

问:

这里有一个类似的问题,但该问题中的用户似乎有一个更大的数组或向量。如果我有:

bool boolArray[4];

我想检查是否所有元素都是假的,我可以分别检查 [ 0 ], [ 1 ] , [ 2 ] 和 [ 3 ],或者我可以遍历它。由于(据我所知)false 的值应该为 0,而 0 以外的任何值都是 true,因此我想简单地做:

if ( *(int*) boolArray) { }

这有效,但我意识到它依赖于 bool 是 1 个字节,int 是 4 个字节。如果我强制转换为 (std::uint32_t) 可以吗,还是仍然是一个坏主意?我只是碰巧在一个数组中有 3 或 4 个布尔值,想知道这是否安全,如果不是,是否有更好的方法可以做到这一点。

另外,如果我最终得到超过 4 个布尔值但少于 8 个布尔值,我可以用 std::uint64_t 或无符号长多头或其他东西做同样的事情吗?

C++ 数组 强制转换 布尔值

评论

10赞 πάντα ῥεῖ 8/18/2019
使用?std::bitset
7赞 Zebrafish 8/18/2019
@πάνταῥεῖ我没有考虑过这一点,我只是使用了当时对我来说显而易见的东西,一个数组。顺便说一句,这个网站越来越奇怪了。这可能是一个非常愚蠢的问题,所以我欢迎所有反对票,但我在发布后 5 秒内就收到了反对票。
0赞 πάντα ῥεῖ 8/18/2019
“这可能是一个非常愚蠢的问题,所以我欢迎所有反对票,但我在发布后的 5 秒内就收到了反对票。”对不起,这是我的DV。阅读有关布尔数组或向量的信息给我敲响了警钟。我现在收回了我的DV。
0赞 Zebrafish 8/18/2019
好吧,我猜我会研究 std::bitset。我只是使用了一个数组,因为它对我来说是最明显的,我只是将它用于鼠标左、中和右键。
0赞 Matthieu M. 8/19/2019
我只是将它用于鼠标左、中、右键。=> 您是否考虑过将枚举用作位集并操作?enum class MouseButtonDown: std::uint8_t { Left = 1, Middle = 2, Right = 4 }

答:

22赞 Yksisarvinen 8/18/2019 #1

正如 πάντα ῥεῖ 在评论中指出的那样,std::bitset 可能是以无 UB 方式处理该问题的最佳方法。

std::bitset<4> boolArray {};
if(boolArray.any()) {
    //do the thing
}

如果你想坚持使用数组,你可以使用 std::any_of,但这需要(可能是读者特有的)使用 functor,它只返回它的参数:

bool boolArray[4];
if(std::any_of(std::begin(boolArray), std::end(boolArray), [](bool b){return b;}) {
    //do the thing
}

类型双关语 4 秒可能是一个坏主意 - 您无法确定每种类型的大小。它可能适用于大多数架构,但可以保证在任何情况下都能在任何地方工作。boolintstd::bitset

评论

0赞 Zebrafish 8/18/2019
“订单仓位从最右边的位开始计算”,所以如果我做 std::bitset<4> foo;foo[1] = 真;COUT 的输出为 0010。我知道这无关紧要,但你知道为什么第一个索引是最后一位吗?
1赞 Yksisarvinen 8/18/2019
@Zebrafish 可能是为了让它更类似于 clasical 位操作。 等效于 。这样,您可以更轻松地从旧的 C 屏蔽位方式转移到 C++。我想存储布尔数组不是它的主要目的;)myBitset[3]myInt & (1 << 3)std::bitset
0赞 historystamp 8/19/2019
我使用了一个可以自动执行此操作的编译器。因此,您可能想查看编译器如何优化位检查。
0赞 pjc50 8/19/2019
可以用 godbolt 确认第一个优化得很好,但第二个没有:godbolt.org/z/t97Dr9
2赞 Jesper Juhl 8/18/2019 #2

标准库以 std::all_of、std::any_of、std::none_of 算法的形式提供您需要的内容。

4赞 Oblivion 8/18/2019 #3

您可以使用 std::bitset<N>::any

如果任何位设置为 ,则返回 ,否则 。truetruefalse

#include <iostream>      
#include <bitset>        

int main ()
{
  std::bitset<4> foo;
  // modify foo here


  if (foo.any())
    std::cout << foo << " has " << foo.count() << " bits set.\n";
  else
    std::cout << foo << " has no bits set.\n";

  return 0;
}

如果要返回所有位或不将所有位设置为 on,则可以分别使用 或。truestd::bitset<N>::allstd::bitset<N>::none

1赞 Justin Time - Reinstate Monica 8/19/2019 #4

...对于强制性的“滚动你自己的”答案,我们可以为任何数组提供一个简单的类似“或”的函数,如下所示:bool[N]

template<size_t N>
constexpr bool or_all(const bool (&bs)[N]) {
    for (bool b : bs) {
        if (b) { return b; }
    }

    return false;
}

或者更简洁地说,

template<size_t N>
constexpr bool or_all(const bool (&bs)[N]) {
    for (bool b : bs) { if (b) { return b; } }
    return false;
}

这也具有短路(如 )和在编译时可计算时完全优化的优点。||


除此之外,如果你想检查类型双关语的原始想法到其他类型以简化观察,我非常建议你不要这样做,而是把它看作是 .这将允许您提供一个简单的表示查看器,该查看器可以自动缩放到查看对象的实际大小,允许迭代其各个字节,并允许您更轻松地确定表示是否与特定值匹配(例如,零或非零)。我不完全确定这样的检查是否会调用任何 UB,但我可以肯定地说,任何此类类型的构造都不可能是可行的常量表达式,因为需要重新解释转换为 or 或类似(显式或 in),因此不能轻易优化。bool[N]char[N2]N2 == (sizeof(bool) * N)char*unsigned char*std::memcpy()

评论

0赞 Zebrafish 8/19/2019
哇,我刚刚了解到您可以使用基于范围的for循环和简单的数组。我认为它基于 begin() 和 end()。谢谢。
1赞 Lightness Races in Orbit 8/19/2019
@Zebrafish 它是,但是,是为数组定义的。std::begin()std::end()
10赞 Kevin 8/19/2019 #5

有几个答案已经解释了好的替代方案,特别是 和 .我单独写信是为了指出,除非你知道我们不知道的东西,否则以这种方式在 和 之间输入双关语是不安全的,原因如下:std::bitsetstd::any_of()boolint

  1. int正如多个答案所指出的那样,可能不是四个字节。
  2. M.M在评论中指出,可能不是一个字节。我不知道任何现实世界的架构曾经出现过这种情况,但它仍然是规范合法的。它(可能)不能小于一个字节,除非编译器正在用它的内存模型做一些非常复杂的隐藏球的诡计,而多字节布尔值似乎相当无用。但请注意,字节首先不必是 8 位。bool
  3. int可以有陷阱表示。也就是说,某些位模式在被强制转换为 时会导致未定义的行为是合法的。这在现代架构中很少见,但可能会出现在(例如)ia64 或任何带有符号零的系统上。int
  4. 无论您是否需要担心上述任何一项,您的代码都违反了严格的别名规则因此编译器可以在假设 bools 和 int 是完全独立的对象且生存期不重叠的情况下自由地“优化”它。例如,编译器可能会确定初始化布尔数组的代码是死存储并消除它,因为在取消引用指针之前的某个时间点,布尔“必须”不复存在*。还可能出现与寄存器重用和加载/存储重新排序相关的更复杂的情况。C++ 标准明确允许所有这些不合格行为,该标准表示,当您进行这种类型的双关语时,行为是未定义的。

您应该使用其他答案提供的替代解决方案之一。


* 通过将内存转换为 int 并存储一个整数来重用指向的内存是合法的(有一些限制,特别是在对齐方面),尽管如果你真的想这样做,如果你想稍后读取生成的 int,则必须通过 std::launder。无论如何,编译器有权在看到读取后假定您已经执行了此操作,即使您没有调用 launder。boolArrayboolArray

评论

0赞 Martin Bonner supports Monica 8/19/2019
如果访问单个字节明显较慢(例如,如果将 32 位数量加载到寄存器中,然后访问适当的字节),则布尔值可能会增加多字节。