使用"for(auto&e : cont)"安全吗?矢量有什么问题<bool>?

Is it safe to use `for(auto& e : cont)`? What is wrong with vector<bool>?

本文关键字:什么 lt gt bool 问题 安全 auto for 使用 cont      更新时间:2023-10-16

我发现for (auto& e : cont)有时被用来代替普通的for (auto e : cont)(其中cont是一些容器,例如std::vector)。到目前为止,我发现了两个原因:

  1. 引用应该避免复制对象(更快的执行)
  2. 某些类(如std::thread)可能禁止复制

经过几次测试,我可以看到:

  1. for (auto& e : cont)可与除std::vector<bool>外的任何std::vector<T>兼容
  2. for (cont::reference e : cont) 适用于任何std::vector<T> ,包括std::vector<bool> (这里明显的问题是:
    我应该使用这个而不是for (auto& e : cont)吗?
  3. std::vector<bool>不被认为是真正的容器,许多人认为它应该重命名(实现是好的和有用的,但应该有不同的名称,如bitsetbitfielddynamic_bitset)
  4. std::vector<bool>可以以for (auto& e : cont)也可以工作的方式实现(见下面我的尝试)

下面是我用于测试的代码:
(诀窍是使用reference& iterator::operator * () { return *this; })

#include <vector>
#include <iostream>
#include <typeinfo>
using namespace std;
#define USE_STD_VECT_BOOL 0
#if USE_STD_VECT_BOOL
typedef vector<bool> BITS;
#else
typedef class bvect {
    unsigned data; // we could use vector<unsigned> but this is just an examle
    unsigned size;
public:
    bvect(): data(0), size(0) {}
    void push_back(bool value) {
        if(value) data |= (1u<<size);
        size++; }
    class reference {
        friend class bvect;
    protected:
        unsigned& data;
        unsigned  flag;
        reference(unsigned& data, unsigned flag)
        : data(data), flag(flag) {}
    public:
        operator bool() const {
            return data & flag; }
        reference& operator = (bool value) {
            if(value) data |= flag;
            else data &= ~flag;
            return *this; }
    };
    class iterator: protected reference  {
        friend class bvect;
        iterator(unsigned& data, unsigned flag)
        : reference(data, flag) {}
    public:
        typedef bool value_type;
        typedef bvect::reference reference;
        typedef input_iterator_tag iterator_category;
    //  HERE IS THE TRICK:
        reference& operator * () {
            return *this; }
        iterator& operator ++ () {
            flag <<= 1;
            return *this; }
        iterator operator ++ (int) {
            iterator tmp(*this);
            operator ++ ();
            return tmp; }
        bool operator == (const iterator& rhs) {
            return flag == rhs.flag; }
        bool operator != (const iterator& rhs) {
            return flag != rhs.flag; }
    };
    iterator begin() {
        return iterator(data, 1); }
    iterator end() {
        return iterator(data, 1<<size); }
} BITS;
#endif
int main() {
    BITS bits;
    bits.push_back(0);
    bits.push_back(1);
#if !USE_STD_VECT_BOOL
//  won't compile for vector<bool>
    for(auto& a : bits)
        cout << typeid(a).name()
          << " = " << (int)(bool)a
          << endl;
#endif
//  std::_Bit_Reference
    for(BITS::reference a : bits)
        cout << typeid(a).name()
          << " = " << (int)(bool)a
          << endl;
//  few more tests
    for(auto a : bits)
        cout << (int)(bool)a;
    for(bool a : bits)
        cout << (int)(bool)a;
    cout << endl;
}

问题:

  1. 应该使用for (cont::reference e : cont)而不是for (auto& e : cont)吗?
  2. 这个把戏有什么问题吗?它能被增强到适用于任何用例吗?
    编辑:这里我指的是bvect::reference& bvect::iterator::operator * () { return *this; }
  3. 可以/应该改变STL吗?(参考vector<bool>)

反馈:答案和评论:

  1. 使用for (auto&& e : cont)(用于写入)或for (const auto& e : cont)(用于读取/枚举)似乎在所有情况下都有效。(感谢dyp和Praetorian)
  2. 使用typename iterator_traits<decltype(begin(cont))>::reference似乎甚至可以用于数组(cont=boo[2])。(是的,它是丑陋的,但可以缩短使用一些模板别名我认为。我想不出有什么反例需要这样做,所以,就目前而言,这不是解决方案。auto&&)
  3. 标准规定iterator::operator * ()必须返回iterator::reference(而不是iterator::reference&),但仍然不知道为什么。

最后判决:

auto it = bits.begin();
auto&& e = *it; cout << (bool)e;
it++; cout << (bool)e;
cout << endl;
输出:

10

这绝对是糟糕的。我们应该坚持使用标准(iterator::operator * ()必须返回iterator::reference)。谢谢

vector<bool>vector类模板的专门化,它将布尔值存储在位域中以进行空间优化。因为您不能返回对位字段的引用,所以vector<bool>::reference是一个类类型,一个代表单个bool的代理。vector<bool>::operator[]按值返回代理类实例;这同样适用于解引用vector<bool>::iterator

vector<bool> cont;
for (auto& e : cont) { ... }

这里你试图将一个左值引用绑定到一个右值,这是不允许的。

我应该使用for (cont::reference e : cont)而不是for (auto& e : cont)吗?
这个把戏有什么问题吗?它能被增强到适用于任何用例吗?

基于范围的for的好处是它也适用于普通的C数组。使用cont::reference将失败,对于这些,以及任何没有名为reference的成员类型的可迭代类型。如果你想在循环中只读访问容器元素,你应该使用for(auto const& e : cont);如果你想修改元素,你应该使用for(auto&& e : cont)

在后一种情况下,auto&& e是一个通用引用,可以绑定到左值和右值,所以它也适用于vector<bool>的情况。

对于空间优化,vector<bool>使用仅存储一位bool值(而不是像bool那样存储一个字节)。你不能在字节中获得位的地址,因为operator[]这样的东西不起作用。在你的例子中,你不能在这个向量中逐位迭代。你最好使用set<bool>。有很多关于vector<bool>是否应该在STL的讨论