TL;DR:请考虑以下准则:
-
For 观察元素,使用以下语法:
for (const auto& elem : container) // capture by const reference
-
For 修改元素就位,使用:
for (auto& elem : container) // capture by (non-const) reference
当然,如果有需要的话,可以制作一个本地副本循环体内元素的,捕获by value (for (auto elem : container)
)是一个不错的选择。
详细讨论
让我们开始区分观察容器中的元素
与修改他们就位。
观察元素
让我们考虑一个简单的例子:
vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v)
cout << x << ' ';
上面的代码打印元素(int
s) 在vector
:
1 3 5 7 9
现在考虑另一种情况,其中向量元素不仅仅是简单的整数,
但更复杂的类的实例,具有自定义复制构造函数等。
// A sample test class, with custom copy semantics.
class X
{
public:
X()
: m_data(0)
{}
X(int data)
: m_data(data)
{}
~X()
{}
X(const X& other)
: m_data(other.m_data)
{ cout << "X copy ctor.\n"; }
X& operator=(const X& other)
{
m_data = other.m_data;
cout << "X copy assign.\n";
return *this;
}
int Get() const
{
return m_data;
}
private:
int m_data;
};
ostream& operator<<(ostream& os, const X& x)
{
os << x.Get();
return os;
}
如果我们使用上面的for (auto x : v) {...}
这个新类的语法:
vector<X> v = {1, 3, 5, 7, 9};
cout << "\nElements:\n";
for (auto x : v)
{
cout << x << ' ';
}
输出类似于:
[... copy constructor calls for vector<X> initialization ...]
Elements:
X copy ctor.
1 X copy ctor.
3 X copy ctor.
5 X copy ctor.
7 X copy ctor.
9
因为可以从输出中读取,复制构造函数调用是在基于范围的 for 循环迭代期间进行的。
这是因为我们是捕获来自容器的元素by value
(the auto x
参与for (auto x : v)
).
This is 效率低下代码,例如,如果这些元素是std::string
,
可以通过昂贵的内存管理器等来完成堆内存分配。
如果我们只是想这样做,这是没有用的observe容器中的元素。
因此,可以使用更好的语法:captureby const
参考, i.e. const auto&
:
vector<X> v = {1, 3, 5, 7, 9};
cout << "\nElements:\n";
for (const auto& x : v)
{
cout << x << ' ';
}
现在输出是:
[... copy constructor calls for vector<X> initialization ...]
Elements:
1 3 5 7 9
没有任何虚假的(并且可能昂贵的)复制构造函数调用。
所以,当观察容器中的元素(即只读访问),
以下语法适用于简单的情况廉价复制类型,例如int
, double
, etc.:
for (auto elem : container)
否则,捕获const
参考资料中比较好一般情况,
避免无用的(并且可能昂贵的)复制构造函数调用:
for (const auto& elem : container)
修改容器中的元素
如果我们想要modify使用基于范围的容器中的元素for
,
以上for (auto elem : container)
and for (const auto& elem : container)
语法错误。
事实上,在前一种情况下,elem
存储一个copy原来的
元素,因此对其所做的修改只会丢失并且不会持久存储
在容器中,例如:
vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v) // <-- capture by value (copy)
x *= 10; // <-- a local temporary copy ("x") is modified,
// *not* the original vector element.
for (auto x : v)
cout << x << ' ';
输出只是初始序列:
1 3 5 7 9
相反,尝试使用for (const auto& x : v)
只是无法编译。
g++ 输出类似如下的错误消息:
TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x'
x *= 10;
^
在这种情况下,正确的方法是通过非捕获const
参考:
vector<int> v = {1, 3, 5, 7, 9};
for (auto& x : v)
x *= 10;
for (auto x : v)
cout << x << ' ';
输出是(如预期):
10 30 50 70 90
This for (auto& elem : container)
语法也适用于更复杂的类型,
例如考虑到vector<string>
:
vector<string> v = {"Bob", "Jeff", "Connie"};
// Modify elements in place: use "auto &"
for (auto& x : v)
x = "Hi " + x + "!";
// Output elements (*observing* --> use "const auto&")
for (const auto& x : v)
cout << x << ' ';
输出是:
Hi Bob! Hi Jeff! Hi Connie!
代理迭代器的特殊情况
假设我们有一个vector<bool>
,我们想要反转逻辑布尔状态
其元素,使用上面的语法:
vector<bool> v = {true, false, false, true};
for (auto& x : v)
x = !x;
上面的代码无法编译。
g++ 输出类似如下的错误消息:
TestRangeFor.cpp:168:20: error: invalid initialization of non-const reference of
type 'std::_Bit_reference&' from an rvalue of type 'std::_Bit_iterator::referen
ce {aka std::_Bit_reference}'
for (auto& x : v)
^
问题是std::vector
模板是专门 for bool
, 与
实施packs the bool
s 优化空间(每个布尔值是
存储在一位中,一个字节中八个“布尔”位)。
因此(因为不可能返回对单个位的引用),vector<bool>
使用所谓的“代理迭代器”图案。
“代理迭代器”是一个迭代器,当取消引用时,它不会not产生一个
普通的bool &
,而是返回(按值)a临时对象,
这是一个代理类可转换为bool http://en.wikipedia.org/wiki/Sequence_container_%28C%2B%2B%29#Specialization_for_bool。
(也可以看看这个问题和相关答案 https://stackoverflow.com/questions/8399417/why-vectorboolreference-doesnt-return-reference-to-bool在 StackOverflow 上。)
就地修改以下元素vector<bool>
,一种新的语法(使用auto&&
)
必须使用:
for (auto&& x : v)
x = !x;
下面的代码工作正常:
vector<bool> v = {true, false, false, true};
// Invert boolean status
for (auto&& x : v) // <-- note use of "auto&&" for proxy iterators
x = !x;
// Print new element values
cout << boolalpha;
for (const auto& x : v)
cout << x << ' ';
和输出:
false true true false
请注意,for (auto&& elem : container)
语法也适用于其他情况
普通(非代理)迭代器(例如,对于vector<int>
or a vector<string>
).
(作为旁注,前面提到的“观察”语法for (const auto& elem : container)
对于代理迭代器的情况也可以正常工作。)
Summary
上述讨论可以总结为以下指导方针:
-
For 观察元素,使用以下语法:
for (const auto& elem : container) // capture by const reference
-
For 修改元素就位,使用:
for (auto& elem : container) // capture by (non-const) reference
当然,如果有需要的话,可以制作一个本地副本循环体内元素的,捕获by value (for (auto elem : container)
)是一个不错的选择。
关于通用代码的附加说明
In 通用代码,因为我们无法对泛型类型做出假设T
复制成本低廉,观察始终使用安全模式for (const auto& elem : container)
.
(这不会触发潜在昂贵的无用副本,对于廉价复制类型也可以很好地工作,例如int
,也适用于使用代理迭代器的容器,例如std::vector<bool>
.)
此外,在修改模式,如果我们想要通用代码为了在代理迭代器的情况下也能工作,最好的选择是for (auto&& elem : container)
.
(这也适用于使用普通非代理迭代器的容器,例如std::vector<int>
or std::vector<string>
.)
So, in 通用代码,可以提供以下指南:
-
For 观察元素,使用:
for (const auto& elem : container)
-
For 修改元素就位,使用:
for (auto&& elem : container)