[英]What is the correct way of using C++11's range-based for?
使用 C++11's range-based for
的正確方法是什么?
應該使用什么語法? for (auto elem : container)
,或for (auto& elem : container)
或for (const auto& elem : container)
? 還是別的?
要觀察元素,請使用以下語法:
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 &&
當然,如果需要在循環體內部制作元素的本地副本,按值捕獲( for (auto elem : container)
)是一個不錯的選擇。
讓我們開始區分觀察容器中的元素和就地修改它們。
讓我們考慮一個簡單的例子:
vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v)
cout << x << ' ';
上面的代碼打印vector
的元素( int
s):
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 循環迭代期間進行復制構造函數調用。
這是因為我們正在按值從容器中捕獲元素( for (auto x : v)
的auto x
部分)。
這是低效的代碼,例如,如果這些元素是std::string
實例,則可以完成堆內存分配,需要昂貴的內存管理器行程等等。如果我們只想觀察容器中的元素,這是無用的。
因此,可以使用更好的語法:通過const
引用捕獲,即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
等:
for (auto elem : container)
否則,在一般情況下通過const
引用捕獲更好,以避免無用(且可能昂貴)的復制構造函數調用:
for (const auto& elem : container)
如果我們想使用基於范圍的for
修改容器中的元素,上面的for (auto elem : container)
和for (const auto& elem : container)
語法是錯誤的。
事實上,在前者的情況下, 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 << ' ';
輸出只是初始序列:
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
這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
模板專門用於bool
,其實現將bool
打包以優化空間(每個布爾值存儲在一位中,一個字節中有八個“布爾”位)。
因此(因為不可能返回對單個位的引用), vector<bool>
使用所謂的“代理迭代器”模式。 “代理迭代器”是一個迭代器,當被取消引用時,它不會產生一個普通的bool &
,而是返回(按值)一個臨時對象,它是一個可轉換為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>
或vector<string>
)。
(作為旁注,前面提到的for (const auto& elem : container)
“觀察”語法也適用於代理迭代器的情況。)
上述討論可以總結為以下指南:
要觀察元素,請使用以下語法:
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 &&
當然,如果需要在循環體內部制作元素的本地副本,按值捕獲( for (auto elem : container)
)是一個不錯的選擇。
在泛型代碼中,由於我們不能假設泛型類型T
復制成本低,因此在觀察模式下始終使用for (const auto& elem : container)
。
(這不會觸發潛在的昂貴的無用副本,也適用於像int
這樣的廉價復制類型,以及使用代理迭代器的容器,如std::vector<bool>
。)
此外,在修改模式下,如果我們希望通用代碼也能在代理迭代器的情況下工作,最好的選擇是for (auto&& elem : container)
。
(這也適用於使用普通非代理迭代器的容器,如std::vector<int>
或std::vector<string>
。)
因此,在通用代碼中,可以提供以下指南:
要觀察元素,請使用:
for (const auto& elem : container)
要修改適當的元素,請使用:
for (auto&& elem : container)
沒有正確的方法來使用for (auto elem : container)
,或者for (auto& elem : container)
或者for (const auto& elem : container)
。 你只是表達你想要的。
讓我詳細說明一下。 我們去散散步吧。
for (auto elem : container) ...
這是語法糖:
for(auto it = container.begin(); it != container.end(); ++it) {
// Observe that this is a copy by value.
auto elem = *it;
}
如果您的容器包含易於復制的元素,則可以使用此方法。
for (auto& elem : container) ...
這是語法糖:
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;
}
例如,當您想直接寫入容器中的元素時,請使用此選項。
for (const auto& elem : container) ...
這是語法糖:
for(auto it = container.begin(); it != container.end(); ++it) {
// You just want to read stuff, no modification
const auto& elem = *it;
}
正如評論所說,僅供閱讀。 就是這樣,如果使用得當,一切都是“正確的”。
正確的方法總是
for(auto&& elem : container)
這將保證保留所有語義。
雖然 range-for 循環的最初動機可能是易於迭代容器的元素,但語法足夠通用,即使對於不是純容器的對象也很有用。
for 循環的語法要求是range_expression
支持begin()
和end()
作為函數——要么作為它評估的類型的成員函數,要么作為采用該類型實例的非成員函數。
作為一個人為的例子,可以使用以下類生成一系列數字並迭代該范圍。
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;
};
具有以下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;
}
一個會得到以下輸出。
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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.