使用`for(auto&e:cont)`是否安全?vector <bool>有什么问题?

fir*_*rda 3 c++ boolean stdvector auto c++11

我发现for (auto& e : cont)有时使用而不是普通for (auto e : cont)(cont例如,某些容器std::vector).到目前为止,我找到了两个原因:

  1. 参考应避免复制对象(执行速度更快)
  2. 某些课程可能禁止制作副本(例如std::thread)

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

  1. for (auto& e : cont)适用于任何std::vector<T>除外std::vector<bool>
  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;
}
Run Code Online (Sandbox Code Playgroud)

问题:

  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;
Run Code Online (Sandbox Code Playgroud)

输出:

10
Run Code Online (Sandbox Code Playgroud)

这绝对是坏事.我们应该坚持标准(iterator::operator * ()必须返回iterator::reference).谢谢 :)

Pra*_*ian 8

vector<bool>vector类模板的特化,它将布尔值存储在位域中以进行空间优化.由于您无法返回对位域的引用,因此vector<bool>::reference是类类型,代表单个的代理bool.vector<bool>::operator[]按值返回此代理类实例; 这同样适用于解除引用a vector<bool>::iterator.

vector<bool> cont;
for (auto& e : cont) { ... }
Run Code Online (Sandbox Code Playgroud)

在这里,您尝试将左值引用绑定到右值,这是不允许的.

我应该使用for (cont::reference e : cont)而不是for (auto& e : cont)
诀窍有什么问题?任何用例都可以增强它吗?

基于范围的优点是它适用于普通的C数组.cont::reference对于那些,以及没有名为成员类型的任何可迭代类型,使用将失败reference.for(auto const& e : cont)如果您希望只读取循环中的容器元素,并且for(auto&& e : cont)想要修改元素,则应该使用.

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