使用 C++11 的基于范围的正确方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15927033/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the correct way of using C++11's range-based for?
提问by Mr.C64
What is the correct way of using C++11's range-based for
?
使用 C++11 的基于范围的正确方法是for
什么?
What syntax should be used? for (auto elem : container)
,
or for (auto& elem : container)
or for (const auto& elem : container)
?
Or some other?
应该使用什么语法?for (auto elem : container)
,for (auto& elem : container)
或for (const auto& elem : container)
? 还是别的?
回答by Mr.C64
Let's start differentiating between observingthe elements in the container vs. modifyingthem in place.
让我们开始区分观察容器中的元素和就地修改它们。
Observing the elements
观察元素
Let's consider a simple example:
让我们考虑一个简单的例子:
vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v)
cout << x << ' ';
The above code prints the elements (int
s) in the vector
:
上面的代码打印了以下元素(int
s)vector
:
1 3 5 7 9
1 3 5 7 9
Now consider another case, in which the vector elements are not just simple integers, but instances of a more complex class, with custom copy constructor, etc.
现在考虑另一种情况,其中向量元素不仅仅是简单的整数,而是更复杂类的实例,具有自定义复制构造函数等。
// 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;
}
If we use the above for (auto x : v) {...}
syntax with this new class:
如果我们在for (auto x : v) {...}
这个新类中使用上述语法:
vector<X> v = {1, 3, 5, 7, 9};
cout << "\nElements:\n";
for (auto x : v)
{
cout << x << ' ';
}
the output is something like:
输出类似于:
[... 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
[... 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
As it can be read from the output, copy constructorcalls are made during range-based for loop iterations.
This is because we are capturingthe elements from the container by value(the auto x
part in for (auto x : v)
).
由于可以从输出中读取,因此在基于范围的 for 循环迭代期间进行复制构造函数调用。
这是因为我们正在按值( 中的部分)从容器中捕获元素。auto x
for (auto x : v)
This is inefficientcode, e.g., if these elements are instances of std::string
,
heap memory allocations can be done, with expensive trips to the memory manager, etc.
This is useless if we just want to observethe elements in a container.
这是低效的代码,例如,如果这些元素是 的实例std::string
,则可以完成堆内存分配,需要昂贵的内存管理器行程等等。如果我们只想观察容器中的元素,这是无用的。
So, a better syntax is available: capture by const
reference, i.e. const auto&
:
因此,可以使用更好的语法:通过const
引用捕获,即const auto&
:
vector<X> v = {1, 3, 5, 7, 9};
cout << "\nElements:\n";
for (const auto& x : v)
{
cout << x << ' ';
}
Now the output is:
现在输出是:
[... copy constructor calls for vector<X> initialization ...] Elements: 1 3 5 7 9
[... copy constructor calls for vector<X> initialization ...] Elements: 1 3 5 7 9
Without any spurious (and potentially expensive) copy constructor call.
没有任何虚假(并且可能很昂贵)的复制构造函数调用。
So, when observingelements in a container (i.e., for read-only access),
the following syntax is fine for simple cheap-to-copytypes, like int
, double
, etc.:
所以,当观察到在一个容器(即用于只读访问)的元素,下面的语法是对简单低价到副本类型,如int
,double
等:
for (auto elem : container)
Else, capturing by const
reference is better in the general case,
to avoid useless (and potentially expensive) copy constructor calls:
否则,const
在一般情况下,通过引用捕获更好,以避免无用(且可能昂贵)的复制构造函数调用:
for (const auto& elem : container)
Modifying the elements in the container
修改容器中的元素
If we want to modifythe elements in a container using range-based for
,
the above for (auto elem : container)
and for (const auto& elem : container)
syntaxes are wrong.
如果我们要修改使用在容器中的元素范围为基础for
,上面for (auto elem : container)
和for (const auto& elem : container)
语法都错了。
In fact, in the former case, elem
stores a copyof the original
element, so modifications done to it are just lost and not stored persistently
in the container, e.g.:
事实上,在前一种情况下,elem
存储原始元素的副本,因此对其所做的修改只会丢失并且不会持久存储在容器中,例如:
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 << ' ';
The output is just the initial sequence:
输出只是初始序列:
1 3 5 7 9
1 3 5 7 9
Instead, an attempt of using for (const auto& x : v)
just fails to compile.
相反,尝试使用for (const auto& x : v)
只是无法编译。
g++ outputs an error message something like this:
g++ 输出类似这样的错误消息:
TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x' x *= 10; ^
TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x' x *= 10; ^
The correct approach in this case is capturing by non-const
reference:
在这种情况下,正确的方法是通过非const
引用捕获:
vector<int> v = {1, 3, 5, 7, 9};
for (auto& x : v)
x *= 10;
for (auto x : v)
cout << x << ' ';
The output is (as expected):
输出是(如预期的那样):
10 30 50 70 90
10 30 50 70 90
This for (auto& elem : container)
syntax works also for more complex types,
e.g. considering a vector<string>
:
此for (auto& elem : container)
语法也适用于更复杂的类型,例如考虑 a 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 << ' ';
the output is:
输出是:
Hi Bob! Hi Jeff! Hi Connie!
Hi Bob! Hi Jeff! Hi Connie!
The special case of proxy iterators
代理迭代器的特殊情况
Suppose we have a vector<bool>
, and we want to invert the logical boolean state
of its elements, using the above syntax:
假设我们有一个vector<bool>
,并且我们想使用上面的语法反转其元素的逻辑布尔状态:
vector<bool> v = {true, false, false, true};
for (auto& x : v)
x = !x;
The above code fails to compile.
上面的代码无法编译。
g++ outputs an error message similar to this:
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) ^
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) ^
The problem is that std::vector
template is specializedfor bool
, with an
implementation that packsthe bool
s to optimize space (each boolean value is
stored in one bit, eight "boolean" bits in a byte).
的问题是,std::vector
模板专门为bool
,与该一个实现包的bool
s到优化空间(每个布尔值被存储在一个比特,一个字节八“布尔”比特)。
Because of that (since it's not possible to return a reference to a single bit),
vector<bool>
uses a so-called "proxy iterator"pattern.
A "proxy iterator" is an iterator that, when dereferenced, does notyield an
ordinary bool &
, but instead returns (by value) a temporary object,
which is a specialization on Wikipedia">proxy classconvertible to bool
.
(See also ::reference doesn't return reference to bool?">this question and related answershere on StackOverflow.)
因此(因为不可能返回对单个位的引用),
vector<bool>
使用所谓的“代理迭代器”模式。“代理迭代器”是一种迭代器,在取消引用时,它不会产生普通的bool &
,而是返回(按值)一个临时对象,该对象是specialization on Wikipedia">可转换为 的专业化">代理类bool
。(另请参阅StackOverflow 上的::reference 不返回对 bool 的引用?">此问题和相关答案。)
To modify in place the elements of vector<bool>
, a new kind of syntax (using auto&&
)
must be used:
要就地修改 的元素vector<bool>
,auto&&
必须使用一种新的语法(使用):
for (auto&& x : v)
x = !x;
The following code works fine:
以下代码工作正常:
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 << ' ';
and outputs:
和输出:
false true true false
false true true false
Note that the for (auto&& elem : container)
syntax also works in the other cases
of ordinary (non-proxy) iterators (e.g. for a vector<int>
or a vector<string>
).
请注意,该for (auto&& elem : container)
语法也适用于普通(非代理)迭代器的其他情况(例如 avector<int>
或 a vector<string>
)。
(As a side note, the aforementioned "observing" syntax of for (const auto& elem : container)
works fine also for the proxy iterator case.)
(作为旁注,前面提到的“观察”语法for (const auto& elem : container)
也适用于代理迭代器的情况。)
Summary
概括
The above discussion can be summarized in the following guidelines:
上述讨论可以总结为以下指南:
For observingthe elements, use the following syntax:
for (const auto& elem : container) // capture by const reference
If the objects are cheap to copy(like
int
s,double
s, etc.), it's possible to use a slightly simplified form:for (auto elem : container) // capture by value
For modifyingthe elements in place, use:
for (auto& elem : container) // capture by (non-const) reference
If the container uses "proxy iterators"(like
std::vector<bool>
), use:for (auto&& elem : container) // capture by &&
要观察元素,请使用以下语法:
for (const auto& elem : container) // capture by const reference
如果对象的复制成本很低(如
int
s、double
s 等),则可以使用稍微简化的形式:for (auto elem : container) // capture by value
要修改适当的元素,请使用:
for (auto& elem : container) // capture by (non-const) reference
如果容器使用“代理迭代器”(如
std::vector<bool>
),请使用:for (auto&& elem : container) // capture by &&
Of course, if there is a need to make a local copyof the element inside the loop body, capturing by value(for (auto elem : container)
) is a good choice.
当然,如果需要对循环体内部的元素进行本地拷贝,按值( for (auto elem : container)
)捕获是一个不错的选择。
Additional notes on generic code
关于通用代码的附加说明
In generic code, since we can't make assumptions about generic type T
being cheap to copy, in observingmode it's safe to always use for (const auto& elem : container)
.
(This won't trigger potentially expensive useless copies, will work just fine also for cheap-to-copy types like int
, and also for containers using proxy-iterators, like std::vector<bool>
.)
在泛型代码中,由于我们不能假设泛型类型的T
复制成本很低,因此在观察模式下始终使用for (const auto& elem : container)
.
(这不会触发潜在的昂贵的无用副本,对于廉价复制类型(如int
)以及使用代理迭代器的容器(如std::vector<bool>
)也能正常工作。)
Moreover, in modifyingmode, if we want generic codeto work also in case of proxy-iterators, the best option is for (auto&& elem : container)
.
(This will work just fine also for containers using ordinary non-proxy-iterators, like std::vector<int>
or std::vector<string>
.)
此外,在修改模式下,如果我们希望通用代码也能在代理迭代器的情况下工作,最好的选择是for (auto&& elem : container)
.
(这也适用于使用普通非代理迭代器的容器,例如std::vector<int>
或std::vector<string>
。)
So, in generic code, the following guidelines can be provided:
因此,在通用代码中,可以提供以下指南:
For observingthe elements, use:
for (const auto& elem : container)
For modifyingthe elements in place, use:
for (auto&& elem : container)
要观察元素,请使用:
for (const auto& elem : container)
要修改适当的元素,请使用:
for (auto&& elem : container)
回答by Mr.C64
There is no correct wayto use for (auto elem : container)
, or for (auto& elem : container)
or for (const auto& elem : container)
. You just express what you want.
没有正确的方法来使用for (auto elem : container)
, for (auto& elem : container)
or for (const auto& elem : container)
。你只是表达你想要的。
Let me elaborate on that. Let's take a stroll.
让我详细说明一下。我们去散散步吧。
for (auto elem : container) ...
This one is syntactic sugar for:
这是语法糖:
for(auto it = container.begin(); it != container.end(); ++it) {
// Observe that this is a copy by value.
auto elem = *it;
}
You can use this one if it your container contains elements which are cheap to copy.
如果您的容器包含易于复制的元素,则可以使用此方法。
for (auto& elem : container) ...
This one is syntactic sugar for:
这是语法糖:
for(auto it = container.begin(); it != container.end(); ++it) {
// Now you're directly modifying the elements
// because elem is an lvalue reference
auto& elem = *it;
}
Use this when you want to write to the elements in the container directly, for example.
例如,当您想直接写入容器中的元素时,请使用此选项。
for (const auto& elem : container) ...
This one is syntactic sugar for:
这是语法糖:
for(auto it = container.begin(); it != container.end(); ++it) {
// You just want to read stuff, no modification
const auto& elem = *it;
}
As the comment says, just for reading. And that's about it, everything is "correct" when used properly.
正如评论所说,仅供阅读。就是这样,如果使用得当,一切都是“正确的”。
回答by Puppy
The correct means is always
正确的方法总是
for(auto&& elem : container)
This will guarantee the preservation of all semantics.
这将保证保留所有语义。
回答by R Sahu
While the initial motivation of the range-for loop might have been ease of iterating over the elements of a container, the syntax is generic enough to be useful even for objects that are not purely containers.
虽然 range-for 循环的最初动机可能是易于迭代容器的元素,但语法足够通用,即使对于不是纯容器的对象也很有用。
The syntactic requirement for the for-loop is that range_expression
support begin()
and end()
as either functions -- either as member functions of the type that it evaluates to or as non-member functions what take an instance of the type.
for 循环的语法要求是range_expression
支持begin()
和end()
作为任一函数——要么作为它评估的类型的成员函数,要么作为采用该类型实例的非成员函数。
As a contrived example, one can generate a range of numbers and iterate over the range using the following class.
作为一个人为的例子,可以使用以下类生成一系列数字并迭代该范围。
struct Range
{
struct Iterator
{
Iterator(int v, int s) : val(v), step(s) {}
int operator*() const
{
return val;
}
Iterator& operator++()
{
val += step;
return *this;
}
bool operator!=(Iterator const& rhs) const
{
return (this->val < rhs.val);
}
int val;
int step;
};
Range(int l, int h, int s=1) : low(l), high(h), step(s) {}
Iterator begin() const
{
return Iterator(low, step);
}
Iterator end() const
{
return Iterator(high, 1);
}
int low, high, step;
};
With the following main
function,
具有以下main
功能,
#include <iostream>
int main()
{
Range r1(1, 10);
for ( auto item : r1 )
{
std::cout << item << " ";
}
std::cout << std::endl;
Range r2(1, 20, 2);
for ( auto item : r2 )
{
std::cout << item << " ";
}
std::cout << std::endl;
Range r3(1, 20, 3);
for ( auto item : r3 )
{
std::cout << item << " ";
}
std::cout << std::endl;
}
one would get the following output.
一个会得到以下输出。
1 2 3 4 5 6 7 8 9
1 3 5 7 9 11 13 15 17 19
1 4 7 10 13 16 19