16-12 std::vector<bool>

在第O.1课——通过std::bitset实现位标志与位操作中,我们探讨了std::bitset如何将8个布尔值压缩至一个字节。这些位值可通过std::bitset的成员函数进行修改。

std::vector 还藏着一个有趣的诀窍:其 std::vector 的特殊实现同样能将 8 个布尔值压缩至一个字节,从而可能更高效地存储布尔值。

面向高级读者的说明:
当模板类针对特定模板类型参数采用不同实现时,称为类模板特化class template specialization。本主题将在第26.4节——类模板特化中深入探讨。

与专为位操作设计的std::bitset不同,std::vector不提供位操作成员函数。


使用 std::vector

在多数情况下,std::vector 的工作方式与普通的 std::vector 完全相同:

#include <iostream>
#include <vector>

int main()
{
    std::vector<bool> v { true, false, false, true, true };

    for (int i : v)
        std::cout << i << ' ';
    std::cout << '\n';

    // Change the Boolean value with index 4 to false
    v[4] = false;

    for (int i : v)
        std::cout << i << ' ';
    std::cout << '\n';

    return 0;
}

在作者的64位机器上,这段代码输出:

image


std::vector 的权衡取舍

然而,std::vector 存在一些用户需要注意的权衡点。

首先,std::vector 的开销相当高(在作者机器上 sizeof(std::vector) 为 40 字节),因此除非分配的布尔值数量超过架构的开销阈值,否则无法节省内存。

其次,其性能高度依赖具体实现(各实现甚至无需进行优化,更遑论优化质量)。据本文所述,高度优化的实现可显著快于其他方案,但优化不良的实现则会拖慢速度。

第三点也是最重要的一点:std::vector 并非真正的向量(其内存布局不必连续),也不存储布尔值(实际存储的是位集合),更不符合 C++ 对容器的定义。

尽管 std::vector 在多数场景下表现得像向量,但它与标准库其他部分并不完全兼容。适用于其他元素类型的代码可能无法在 std::vector 上运行。

例如,当 T 为除 bool 以外的任何类型时,以下代码可正常运行:

template<typename T>
void foo( std::vector<T>& v )
{
    T& first = v[0]; // get a reference to the first element
    // Do something with first
}

image


避免使用 std::vector

现代共识认为应尽量避免使用 std::vector,因为其性能提升往往无法弥补因非标准容器导致的兼容性问题。

遗憾的是,这种优化的 std::vector 默认处于启用状态,且无法禁用它以转用实际具备容器特性的非优化版本。业界已呼吁弃用 std::vector,并正在探索替代方案——一种紧凑的布尔向量(可能作为未来的 std::dynamic_bitset 实现)。

我们的建议如下:

当满足以下条件时,请使用 (constexpr) std::bitset:

  • 当所需位数在编译时已知、存储的布尔值数量不超过中等规模(例如低于64k),且有限的运算符和成员函数集(例如缺乏迭代器支持)能满足需求时,请使用(constexpr) std::bitset。

  • 当需要一个可调整大小的布尔值容器且无需节省空间时,请优先选择 std::vector。该类型行为与普通容器无异。

  • 当需要动态位集进行位运算时,建议采用第三方实现的动态位集(如 boost::dynamic_bitset)。此类类型不会伪装成标准库容器,因为它们本身就不是。

最佳实践:
相较于 std::vector,优先选用 constexpr std::bitset、std::vector 或第三方动态位集实现。

posted @ 2026-01-08 10:00  游翔  阅读(14)  评论(0)    收藏  举报