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 の価値は短い構文より、データの流れを段階として表現できることです。パイプラインが長い、または状態変更が混ざる場合は中間処理に名前を付けるか、明示的なアルゴリズムへ戻します。