使用"for(auto&e : cont)"安全吗?矢量有什么问题<bool>?
Is it safe to use `for(auto& e : cont)`? What is wrong with vector<bool>?
我发现for (auto& e : cont)
有时被用来代替普通的for (auto e : cont)
(其中cont
是一些容器,例如std::vector
)。到目前为止,我发现了两个原因:
- 引用应该避免复制对象(更快的执行)
- 某些类(如
std::thread
)可能禁止复制
经过几次测试,我可以看到:
-
for (auto& e : cont)
可与除std::vector<bool>
外的任何std::vector<T>
兼容 -
for (cont::reference e : cont)
适用于任何std::vector<T>
,包括std::vector<bool>
(这里明显的问题是:
我应该使用这个而不是for (auto& e : cont)
吗? -
std::vector<bool>
不被认为是真正的容器,许多人认为它应该重命名(实现是好的和有用的,但应该有不同的名称,如bitset
或bitfield
或dynamic_bitset
) -
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;
}
问题:
- 应该使用
for (cont::reference e : cont)
而不是for (auto& e : cont)
吗? - 这个把戏有什么问题吗?它能被增强到适用于任何用例吗?
编辑:这里我指的是bvect::reference& bvect::iterator::operator * () { return *this; }
- 可以/应该改变STL吗?(参考
vector<bool>
)
反馈:答案和评论:
- 使用
for (auto&& e : cont)
(用于写入)或for (const auto& e : cont)
(用于读取/枚举)似乎在所有情况下都有效。(感谢dyp和Praetorian) - 使用
typename iterator_traits<decltype(begin(cont))>::reference
似乎甚至可以用于数组(cont=boo[2])。(是的,它是丑陋的,但可以缩短使用一些模板别名我认为。我想不出有什么反例需要这样做,所以,就目前而言,这不是解决方案。auto&&
) - 标准规定
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
的讨论
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- 警告处理为错误这里有什么问题
- 什么时候调用组成单元对象的析构函数
- #定义c-预处理器常量..我做错了什么
- 努力将整数转换为链表。不知道我在这里做错了什么
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 什么时候在C++中返回常量引用是个好主意
- 当在同一名称空间中有两个具有相同签名的函数时,会发生什么
- C++避免重复声明的语法是什么
- c++库的公共头文件中应该包含什么
- 问题:什么是QAbstractItemView::NoEditTriggers的反面
- 有什么方法可以遍历结构吗
- 当类在C++中定义时,有什么方法可以"register"类吗?
- ifstream什么都没读
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- 什么是模板&lt;&gt;inline bla bla
- &lt;&lt;&lt;的这些超载有什么区别操作员
- 这是做什么的?如果(无符号(dx) & lt;无符号(大小))
- 什么会导致死亡<<变量& lt; & lt;“ t";当输出超过终端宽度时,打印空白而不是值