基於 range 的 for 循環和 auto

基於 range 的 for 循環和 auto

C++11 引入一種循環的新形式,叫基於 range 的 for 循環,它允許我們用更簡單易讀的形式遍歷容器中的所有元素

vector<int> v{1, 2, 3};
for (int i : v) {
    cout << i << endl;
}

可以使用 auto 來讓編譯器來推導元素的類型,上面的循環可以改寫為

for (auto i : v) {
    cout << i << endl;
}

根據 auto 的推導規則,推導出的類型是初始值退化後的類型,即

  • 去掉引用
  • 去掉 const、volatile 限定符
  • 函數和數組將變為指針

根據這個規則,上面循環推導出的類型應該是 int,這對於 int 這種標量類型可能沒有問題,但如果容器里存的是類類型,就可能帶來巨大的拷貝開銷,因為每次做循環都需要創建容器元素的局部副本,這種情況下,應該用 auto &

for (auto& elem : container)    // capture by (non-const) reference

這種形式中修改 elem 將影響容器的內容

對於模板程式碼,總是應該用這種形式,因為你沒法確定模板類型的拷貝開銷是否廉價

如果是只讀的,還應該給 auto 加上 const 限定符

for (const auto& elem : container)    // capture by const reference

代理迭代器

如果容器使用「代理迭代器」(比如 std::vector<bool> ),應該使用

for (auto&& elem : container)    // capture by &&

假設我們想要用 range-for 遍歷一個 std::vector<bool> 並修改它的元素

vector<bool> v = {true, false, false, true};
for (auto& x : v)
    x = !x;

會發現上面這段程式碼無法通過編譯,因為 std::vector 模板對 bool 類型做了模板特化,對 bool 元素做了打包處理以壓縮空間(把 8 個布爾值存到一個位元組里)

由於你無法返回一個 bit 的引用,std::vector<bool> 用了一種叫「代理迭代器」的模式

代理迭代器是一種迭代器,當它被解引用時,它不產生原始的 bool &,而是返回一個臨時對象,它是可以轉換為 bool 的代理類

為了對 std::vector<bool> 使用 range-for 語法,必須使用 auto&& 來引用 bool 元素(關於 auto && 的推導規則請看這篇

這種語法對於沒有使用代理迭代器的容器也適用,因此在泛型程式碼里,最好的選擇就是用這種形式來遍歷修改容器元素

Tags: