C++ 從基礎到精通 — 第 18 講
使用 Ranges 與 Views 建立延遲資料管線
本部落格文章以教學為目的撰寫。 內容可能有誤,請搭配目標編譯器與標準函式庫文件確認。
Iterator 將容器與演算法分離,但傳統呼叫需要反覆傳入 begin 與 end。C++20 Ranges 能直接接收範圍,Views 則能組合不複製資料的延遲轉換。
直接傳入範圍
std::vector<int> scores{30, 80, 50, 100};
std::ranges::sort(scores);
auto found = std::ranges::find(scores, 80);
if (found != scores.end())
{
// 找到值。
}
不必分別寫 begin 與 end,可降低混用不同容器 iterator 的風險。projection 也能直接指定成員。
std::ranges::sort(players, {}, &Player::Score);
View 在需要時才計算
auto visibleNames = players
| std::views::filter([](const Player& p) { return p.IsVisible; })
| std::views::transform([](const Player& p) -> const std::string&
{
return p.Name;
})
| std::views::take(10);
for (const std::string& name : visibleNames)
DrawName(name);
此管線不會建立三個中間 vector。迭代要求元素時才執行 filter 與 transform,take(10) 最多處理十筆。
多數 View 不擁有來源資料
auto MakeBadView()
{
std::vector<int> local{1, 2, 3};
return local | std::views::filter([](int value) { return value > 1; });
}
回傳的 View 參考區域 vector,但函式結束後 vector 已被銷毀。之後迭代會存取失效物件。
儲存或回傳 View 前,必須證明來源 range 的生命週期涵蓋所有使用。如果結果需要獨立擁有資料,就明確收集到容器中。
延遲求值也會把錯誤與副作用推遲到迭代時。修改來源容器仍可能讓 iterator 失效。
Ranges 的價值是用組合描述資料流程,而不只是縮短語法。管線過長或混入狀態修改時,應命名中間階段或改用明確的演算法與迴圈。