为啥 vector<bool> 不是 STL 容器?

Posted

技术标签:

【中文标题】为啥 vector<bool> 不是 STL 容器?【英文标题】:Why isn't vector<bool> a STL container?为什么 vector<bool> 不是 STL 容器? 【发布时间】:2013-07-21 14:25:10 【问题描述】:

Scott Meyers 的书Effective STL: 50 Specific Ways to Improvement Your Use of the Standard Template Library 的第 18 项说要避免使用 vector &lt;bool&gt;,因为它不是 STL 容器,而且它实际上并不适用bools.

以下代码:

vector <bool> v; 
bool *pb =&v[0];

不会编译,违反了 STL 容器的要求。

错误:

cannot convert 'std::vector<bool>::reference* aka std::_Bit_reference*' to 'bool*' in initialization

vector&lt;T&gt;::operator []返回类型应该是T&amp;,但是为什么vector&lt;bool&gt;是个特例呢?

vector&lt;bool&gt; 真正由什么组成?

物品进一步说:

deque<bool> v; // is a STL container and it really contains bools

这可以用作vector&lt;bool&gt; 的替代品吗?

谁能解释一下?

【问题讨论】:

这是 C++98 中的一个设计错误,现在为了兼容性而保留。 @g-makulik,并不是说使用它不会编译,只是你不能将元素的地址存储在指向bool的指针中,因为元素没有'没有自己的地址。 也许这会有所帮助:***.com/questions/670308/alternative-to-vectorbool @g-makulik std::vector&lt;bool&gt; v; 将编译。 &amp;v[0] 不会(取临时地址)。 vector&lt;bool&gt; 的名声不好,但并非完全有道理:isocpp.org/blog/2012/11/on-vectorbool 【参考方案1】:

出于空间优化的原因,C++ 标准(早在 C++98)明确将 vector&lt;bool&gt; 称为特殊标准容器,其中每个 bool 仅使用一位空间而不是一个字节作为普通 bool会(实现一种“动态位集”)。作为这种优化的交换,它没有提供普通标准容器的所有功能和接口。

在这种情况下,由于您不能获取字节内的位地址,因此诸如operator[] 之类的东西不能返回bool&amp;,而是返回一个允许操作特定位的代理对象题。由于此代理对象不是bool&amp;,因此您不能将其地址分配给bool*,就像在“普通”容器上调用此类操作员的结果一样。反过来,这意味着bool *pb =&amp;v[0]; 不是有效代码。

另一方面,deque 没有调用任何此类特殊化,因此每个 bool 占用一个字节,您可以从 operator[] 获取值返回的地址。

最后请注意,MS 标准库的实现(可以说)是次优的,因为它为 deques 使用了较小的块大小,这意味着使用 deque 作为替代品并不总是正确的答案。

【讨论】:

我们是否有任何其他 STL 容器专门用于或显式调用的其他数据类型? 这适用于 C++11 std::array 吗? @chuckleplant 不,std::array 只是一个围绕 T[n] 原始数组的模板化包装器,带有一些辅助函数,如 size()、复制/移动语义和迭代器以使其成为 STL-兼容 - 并且(谢天谢地)它没有违反自己的原则(请注意我对这些的怀疑:)“专业化”为“bool”。 只是一个挑剔的选择 - sizeof(bool) 不一定是一个字节。 ***.com/questions/4897844/…【参考方案2】:

vector&lt;bool&gt; 包含压缩形式的布尔值,仅使用一位作为值(而不是 8,bool[] 数组如何做)。在 C++ 中不可能返回对位的引用,因此有一个特殊的帮助器类型,“位引用”,它为您提供内存中某个位的接口,并允许您使用标准运算符和强制转换。

【讨论】:

@PrashantSrivastava deque&lt;bool&gt; 不是专门的,所以它实际上只是一个持有布尔值的双端队列。 @PrashantSrivastava vector&lt;bool&gt; 具有特定的模板实现。我猜想,其他 STL 容器,例如 deque&lt;bool&gt;,没有,所以它们像任何其他类型一样持有 bool-s。 这是一个在 rust 中提出类似问题的问题,他们不允许使用单个位布尔值 ***.com/questions/48875251/…【参考方案3】:

问题在于vector&lt;bool&gt; 返回一个代理引用对象 而不是真正的引用,因此C++98 样式代码bool * p = &amp;v[0]; 无法编译。但是,如果 operator&amp;returns a proxy pointer object 也可以编译带有 auto p = &amp;v[0]; 的现代 C++11。 Howard Hinnant 撰写了 a blog post 详细介绍了使用此类代理引用和指针时的算法改进。

Scott Meyers 在More Effective C++ 中有一个关于代理类的长条目 30。您可以几乎模仿内置类型:对于任何给定类型T,可以使一对代理(例如reference_proxy&lt;T&gt;iterator_proxy&lt;T&gt;)在某种意义上相互一致reference_proxy&lt;T&gt;::operator&amp;()iterator_proxy&lt;T&gt;::operator*() 是彼此的倒数。

但是,在某些时候需要将代理对象映射回其行为类似于T*T&amp;。对于迭代器代理,可以重载operator-&gt;() 并访问模板T 的接口,而无需重新实现所有功能。但是,对于参考代理,您需要重载 operator.(),这在当前的 C++ 中是不允许的(尽管 Sebastian Redl presented such a proposal 在 BoostCon 2013 上)。您可以在引用代理中像.get() 成员一样进行详细的解决方法,或者在引用中实现T 的所有接口(这是为vector&lt;bool&gt;::bit_reference 所做的),但这要么会丢失内置语法或引入没有用于类型转换的内置语义的用户定义转换(每个参数最多可以有一个用户定义的转换)。

TL;DR:不,vector&lt;bool&gt; 不是容器,因为标准需要一个真正的引用,但它可以表现得几乎像一个容器,至少与 C++ 更接近11(自动)比在 C++98 中。

【讨论】:

【参考方案4】:

许多人认为vector&lt;bool&gt; 专业化是一个错误。

在一篇论文中"Deprecating Vestigial Library Parts in C++17" 有一个提议 重新考虑向量部分特化

bool 部分特化的历史由来已久 std::vector 不满足容器要求,并且在 特别是,它的迭代器不满足随机的要求 访问迭代器。先前弃用此容器的尝试是 C++11 被拒绝,N2204。


拒绝的原因之一是不清楚它会做什么 表示弃用模板的特定专业化。那 可以用谨慎的措辞来解决。更大的问题是 (打包的)向量专业化提供了一个重要的 标准库的客户真正寻求的优化,但是 将不再可用。我们不太可能 弃用标准的这一部分,直到更换设施 提出并接受,如N2050。不幸的是,没有这样的 目前正在向 Library Evolution 提供的修订提案 工作组。

【讨论】:

【参考方案5】:

看看它是如何实现的。 STL 大量构建在模板之上,因此标题确实包含它们所做的代码。

例如查看 stdc++ 实现 here。

来自here 的 llvm::BitVector 也很有趣,即使不是符合 stl 的位向量。

llvm::BitVector 的本质是一个名为reference 的嵌套类和适当的运算符重载,以使BitVector 的行为类似于vector,但有一些限制。下面的代码是一个简化的界面,展示了 BitVector 如何隐藏一个名为 reference 的类,以使真正的实现几乎表现得像一个真正的 bool 数组,而无需为每个值使用 1 个字节。

class BitVector 
public:
  class reference 
    reference &operator=(reference t);
    reference& operator=(bool t);
    operator bool() const;
  ;
  reference operator[](unsigned Idx);
  bool operator[](unsigned Idx) const;      
;

这里的代码有很好的属性:

BitVector b(10, false); // size 10, default false
BitVector::reference &x = b[5]; // that's what really happens
bool y = b[5]; // implicitly converted to bool 
assert(b[5] == false); // converted to bool
assert(b[6] == b[7]); // bool operator==(const reference &, const reference &);
b[5] = true; // assignment on reference
assert(b[5] == true); // and actually it does work.

这段代码其实有缺陷,试运行:

std::for_each(&b[5], &b[6], some_func); // address of reference not an iterator

将无法工作,因为assert( (&amp;b[5] - &amp;b[3]) == (5 - 3) ); 将失败(在llvm::BitVector 内)

这是非常简单的 llvm 版本。 std::vector&lt;bool&gt; 也有工作迭代器。 因此调用for(auto i = b.begin(), e = b.end(); i != e; ++i) 将起作用。还有std::vector&lt;bool&gt;::const_iterator

但是,std::vector&lt;bool&gt; 仍然存在限制,使其在某些 情况下的行为有所不同。

【讨论】:

【参考方案6】:

来自http://www.cplusplus.com/reference/vector/vector-bool/

Vector of bool 这是一个特殊版本的vector,用于 用于 bool 类型的元素并针对空间进行优化。

它的行为类似于向量的非专业版本,具有 以下更改:

存储不一定是 bool 值的数组,但库实现可能会优化存储,以便每个值都是 存储在一个位中。 元素不是使用分配器对象构造的,而是直接在内部存储的适当位上设置它们的值。 成员函数翻转和成员交换的新签名。 一种特殊的成员类型,引用,一个类,它通过一个接口访问容器内部存储中的各个位 模拟布尔引用。相反,成员类型 const_reference 是 一个普通的布尔值。 容器使用的指针和迭代器类型不一定既不是指针也不是符合标准的迭代器,尽管它们 应模拟他们的大部分预期行为。

这些更改为这个专业化提供了一个古怪的界面,并且 支持内存优化而不是处理(这可能适合也可能不适合 您的需求)。在任何情况下,都无法实例化 直接用于布尔向量的非专业模板。解决方法 避免使用其他类型(char、unsigned char)或 容器(如双端队列)使用包装器类型或进一步专门用于 特定的分配器类型。

bitset 是一个为固定大小提供类似功能的类 位数组。

【讨论】:

这并不能直接回答问题。充其量,它需要读者推断出此一般摘要中解释的哪些内容使其成为非 STL。

以上是关于为啥 vector<bool> 不是 STL 容器?的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中使用 std::vector<bool> 对象是不是可以接受,还是应该使用替代方法?

vector<bool>的特殊性

vector<bool>的特殊性

为啥 'std::vector<int> b2;'创建一个 1 元素向量,而不是 2 元素向量?

为啥两个向量的大小<bool> bVec = true,false,true,false,true;向量<char> cVec = 'a', 'b', 'c', 'd',

将向量归零的最快方法<vector<bool>>