基於 range 的 for 迴圈和 auto

路過的摸魚俠發表於2022-05-09

基於 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 && 的推導規則請看這篇

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

相關文章