为什么vector <bool>不是STL容器?


99

斯科特·迈耶斯(Scott Meyers)的书《有效的STL:提高标准模板库使用率的50种特定方法》第18条说,要避免vector <bool>使用它,因为它不是STL容器,并且实际上并不适用bool

如下代码:

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

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

错误:

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

vector<T>::operator []返回类型应该是T&,但是为什么会有特殊情况vector<bool>呢?

什么是vector<bool>真正组成的呢?

该项目还说:

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

可以代替vector<bool>吗?

谁能解释一下?


22
这是C ++ 98中的一个设计错误,现已保留以保持兼容性。
Oktalist,

8
@ g-makulik,并不是说它的使用不会编译,只是因为您不能将元素的地址存储在指向的指针中bool,因为该元素没有自己的地址。
克里斯,


1
@ g-makulik std::vector<bool> v;将编译。&v[0]不会(取一个临时地址)。
Oktalist

4
vector<bool>代表不好,但并非完全合理,所以:isocpp.org/blog/2012/11/on-vectorbool
TemplateRex

Answers:


114

出于空间优化的原因,C ++标准(最早可追溯至C ++ 98)明确要求 vector<bool>最早可以为特殊的标准容器,其中每个布尔仅使用一个空间位,而不是普通布尔使用的一个字节(实现一种“动态位集”)。作为这种优化的交换,它没有提供普通标准容器的所有功能和接口。

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

另一方面 deque,没有这样的特殊化要求,因此每个布尔值都占用一个字节,您可以获取从返回的值的地址operator[]

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


5
我们是否还有其他任何专用或显式调用STL容器的数据类型?
P0W

3
这适用于C ++ 11 std :: array <bool>吗?
塞尔吉奥·巴苏尔科

4
@chuckleplant不,std::array仅仅是围绕原始数组的模板包装器,T[n]带有一些辅助函数,例如size()复制/移动语义,以及添加迭代器以使其与STL兼容-并且(感谢)它不违反自己的原则(请注意,对此表示怀疑:)“专业”为“ bool”。
underscore_d

只是一个小巧的选择-sizeof(bool)不一定是一个字节。stackoverflow.com/questions/4897844/...
乌里拉兹

30

vector<bool>包含压缩形式的布尔值,仅使用一位作为值(而不是8 bool []数组的作用)。在c ++中无法返回对位的引用,因此有一种特殊的帮助程序类型“位引用”,它为您提供了内存中某个位的接口,并允许您使用标准的运算符和强制转换。


1
@PrashantSrivastava deque<bool>不是专门的,因此从字面上看只是一个双端队列。
Konrad Rudolph

@PrashantSrivastava vector<bool>具有特定的模板实现。我猜想,其他STL容器(例如)deque<bool>没有,因此它们像其他任何类型一样容纳bool-。
伊万·史密诺夫

这是在问生锈了类似的事情,他们不允许单个位布尔一个问题stackoverflow.com/questions/48875251/...
安迪启动

25

问题在于vector<bool>返回的是代理引用对象,而不是真正的引用,因此C ++ 98样式代码bool * p = &v[0];无法编译。但是,auto p = &v[0];如果operator&返回了代理指针对象,则可以使带有的现代C ++ 11 进行编译。Howard Hinnant撰写了一篇博客文章,详细介绍了使用此类代理引用和指针时的算法改进。

关于代理类,Scott Meyers在“ 更有效的C ++”中有很长的条款30 。你可以很长的路来几乎模仿内建类型:对于任何给定类型T,一对代理(如reference_proxy<T>iterator_proxy<T>)可制成在这个意义上相互一致的reference_proxy<T>::operator&(),并iterator_proxy<T>::operator*()互为倒数。

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

TL; DR:no vector<bool>不是容器,因为该标准需要真实的引用,但是可以使它的行为几乎像容器,至少在C ++ 11(自动)中比在C ++ 98中更紧密。


10

许多人认为vector<bool>专业化是错误的。

在论文“ C ++ 17中不赞成使用Vestigial库的部分”
中,提出了重新考虑矢量部分专业化的建议 。

长期以来,std :: vector的布尔部分专业化不满足容器要求,尤其是其迭代器不满足随机访问迭代器的要求。C ++ 11 N2204拒绝了之前弃用此容器的尝试。


拒绝的原因之一是,不赞成废弃模板的特定专业化意味着什么。可以通过认真措辞解决这一问题。更大的问题是,向量的(打包)专业化提供了标准库的客户真正寻求的重要优化,但不再可用。在提议并接受替代设施(例如N2050)之前,我们不太可能会弃用标准的这一部分。不幸的是,目前没有这样的修订提案提供给图书馆发展工作组。


5

看一下它是如何实现的。STL很大程度上建立在模板上,因此标头确实包含其执行的代码。

例如,在这里查看stdc ++实现。

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

的本质llvm::BitVector是称为的嵌套类,reference并进行了适当的运算符重载,以使BitVector行为类似,但vector有一些限制。下面的代码是一个简化的接口,用于显示BitVector如何隐藏一个名为的类,reference以使真实实现几乎像布尔的真实数组一样工作,而每个值不使用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( (&b[5] - &b[3]) == (5 - 3) );将失败(在内llvm::BitVector

这是非常简单的llvm版本。std::vector<bool>也有有效的迭代器。因此该呼叫for(auto i = b.begin(), e = b.end(); i != e; ++i)将起作用。还有std::vector<bool>::const_iterator

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


3

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

bool的矢量这是vector的专门版本,用于bool类型的元素并优化空间。

它的行为类似于vector的非专业版本,但有以下更改:

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

这些更改为该专业化提供了一个古怪的界面,并且在处理方面优先考虑内存优化(这可能会或可能不会满足您的需求)。在任何情况下,都不可能直接实例化bool的非专业向量模板。避免此范围的解决方法,避免使用其他类型(字符,无符号字符)或容器(例如双端队列)来使用包装器类型,或者进一步专门处理特定的分配器类型。

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


1
这不能直接回答问题。充其量,它要求读者推断出本概述中解释的内容使其成为非STL。
underscore_d 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.