What is the correct way of using C++11's range-based for?
使用C++ 11的基于EDCOX1的范围正确的方法是什么?0?
应该使用什么语法?
让我们开始区分观察容器中的元素而不是在适当的地方修改它们。好的。观察元素
让我们考虑一个简单的例子:好的。
1 2 3 4 | vector<int> v = {1, 3, 5, 7, 9}; for (auto x : v) cout << x << ' '; |
以上代码打印
1 1 3 5 7 9
现在考虑另一种情况,其中向量元素不只是简单的整数,但是一个更复杂的类的实例,带有自定义的复制构造函数等。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | // 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. "; } X& operator=(const X& other) { m_data = other.m_data; cout <<"X copy assign. "; return *this; } int Get() const { return m_data; } private: int m_data; }; ostream& operator<<(ostream& os, const X& x) { os << x.Get(); return os; } |
如果我们在这个新类中使用上面的
1 2 3 4 5 6 7 8 9 | vector<X> v = {1, 3, 5, 7, 9}; cout <<" Elements: "; for (auto x : v) { cout << x << ' '; } |
输出类似于:好的。
1
2
3
4
5
6
7
8
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
由于它可以从输出中读取,因此在循环迭代的范围。这是因为我们按值从容器中捕获元素(
这是低效的代码,例如,如果这些元素是
因此,可以使用更好的语法:通过
1 2 3 4 5 6 7 8 9 | vector<X> v = {1, 3, 5, 7, 9}; cout <<" Elements: "; for (const auto& x : v) { cout << x << ' '; } |
现在输出为:好的。
1
2
3
4 [... copy constructor calls for vector<X> initialization ...]
Elements:
1 3 5 7 9
没有任何虚假的(潜在的昂贵的)复制构造函数调用。好的。
因此,当观察容器中的元素(即只读访问)时,以下语法对于简单的、便宜的复制类型(如
1 | for (auto elem : container) |
否则,在一般情况下,用
1 | for (const auto& elem : container) |
修改容器中的元素
如果我们想使用基于范围的
实际上,在前一种情况下,
1 2 3 4 5 6 7 | 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 1 3 5 7 9
相反,使用
G++输出如下错误消息:好的。
1
2
3 TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x'
x *= 10;
^
在这种情况下,正确的方法是通过非
1 2 3 4 5 6 | vector<int> v = {1, 3, 5, 7, 9}; for (auto& x : v) x *= 10; for (auto x : v) cout << x << ' '; |
输出(如预期):好的。
1 10 30 50 70 90
这种
1 2 3 4 5 6 7 8 9 | 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 << ' '; |
输出是:好的。
1 Hi Bob! Hi Jeff! Hi Connie!
代理迭代器的特殊情况
假设我们有一个
1 2 3 | vector<bool> v = {true, false, false, true}; for (auto& x : v) x = !x; |
以上代码编译失败。好的。
G++输出一条与此类似的错误消息:好的。
1
2
3
4
5 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)
^
问题是,
因此(因为不可能返回对单个位的引用),
修改
1 2 | for (auto&& x : v) x = !x; |
以下代码工作正常:好的。
1 2 3 4 5 6 7 8 9 10 | 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 << ' '; |
输出:好的。
1 false true true false
注意,
(作为补充说明,前面提到的
上述讨论可概括为以下指导方针:好的。
要观察元素,请使用以下语法:好的。
1 | for (const auto& elem : container) // capture by const reference |
如果复制对象很便宜(如
int s、double s等),可以使用稍微简化的形式:好的。1for (auto elem : container) // capture by value&(a)好的。
要就地修改元素,请使用:好的。
1 | for (auto& elem : container) // capture by (non-const) reference |
如果容器使用"代理迭代器"(如
std::vector ),请使用:好的。1for (auto&& elem : container) // capture by &&
当然,如果需要对循环体中的元素进行本地复制,那么通过值(
在通用代码中,由于我们不能假设通用类型
此外,在修改模式下,如果我们希望通用代码在代理迭代器的情况下也能工作,最好的选择是
因此,在通用代码中,可以提供以下准则:好的。
要观察这些元素,请使用:好的。
1 | for (const auto& elem : container) |
要就地修改元素,请使用:好的。
1 | for (auto&& elem : container) |
好啊。
没有正确的方法来使用
让我详细说明一下。我们去散步吧。
1 | for (auto elem : container) ... |
这一个是句法上的糖分:
1 2 3 4 5 6 | for(auto it = container.begin(); it != container.end(); ++it) { // Observe that this is a copy by value. auto elem = *it; } |
如果容器中包含复制成本较低的元素,则可以使用此方法。
1 | for (auto& elem : container) ... |
这一个是句法上的糖分:
1 2 3 4 5 6 7 | 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; } |
例如,当您希望直接写入容器中的元素时,可以使用此选项。
1 | for (const auto& elem : container) ... |
这一个是句法上的糖分:
1 2 3 4 5 6 | for(auto it = container.begin(); it != container.end(); ++it) { // You just want to read stuff, no modification const auto& elem = *it; } |
正如评论所说,只是为了阅读。就这样,只要使用得当,一切都是"正确的"。
正确的方法总是
1 | for(auto&& elem : container) |
这将保证所有语义的保留。
虽然循环范围的最初动机可能是易于在容器的元素上迭代,但语法足够通用,即使对于不是纯粹容器的对象也非常有用。
for循环的语法要求是,
作为一个人为的例子,可以生成一个数字范围,并使用下面的类在该范围内迭代。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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; }; |
具有以下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #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; } |
我们可以得到以下输出。
1 2 3 | 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 |