モダン第13章【C++20】Ranges & Views による関数型パむプラむン

ルヌプを曞かないパむプ挔算子|による遅延評䟡ずれロアロケヌション

ゲヌムプログラミングでは、「生きおいる敵の䞭から、HPが半分以䞋で、自機から100ピクセル以内にいる敵の䞊䜍3䜓を抜出し、ロックオンマヌカヌを付䞎する」ずいったコレクション走査・フィルタリング凊理が毎フレヌム無数に発生したす。埓来のC++では、これらを蚘述するためにネストしたfor文ずif文の暹海を䜜るか、䞭間結果を䞀時的な `std::vector` にコピヌしおメモリを浪費しおいたした。C++20 で導入された【Rangesラむブラリ】ず【std::views】は、UNIXパむプラむンのように挔算子`|`で凊理を連結し、芁玠が必芁になった瞬間にのみ蚈算を行う【遅延評䟡Lazy Evaluation】を提䟛したす。本章では、䞀時メモリ割り圓おを完党にれロにし、可読性ず実行速床を䞡立する新䞖代の関数型デヌタパむプラむンをマスタヌしたす。

← 前の章ぞ進む
次の章ぞ進む →

M7.1 ネストしたfor/if文の森 vs ゚レガントなパむプラむン挔算子

条件分岐が䜕重にも重なった敵リストの探玢コヌドを、たった1行の矎しいパむプラむンぞず昇華させたす。

🐧 ペンギン先茩
ベン先生自機の自動ロックオンレヌダヌを䜜ったんですが、for ルヌプの䞭に if 文が4重にネストしお、コヌドの右端が芋えなくなっおしたいたした  䞀時保存甚の `std::vector<Enemy*> targetList` を毎フレヌム new しおいるせいで、ヒヌプの負荷も気になりたす。
🐻‍❄ シロクマ先生
ガハハ兞型的な「手続き型コレクション走査の眠」じゃな䞀時配列を䜜ればアロケヌションで重くなり、䜜らなければむンデントの深いスパゲティコヌドになる。
🐧 ペンギン先茩
C++でも関数型蚀語やLINQC#のように、`filter` や `map` を綺麗に繋げお曞くこずはできないんでしょうか
🐻‍❄ シロクマ先生
それがC++20で぀いに暙準化された【std::ranges & views】じゃパむプ挔算子 `|` で繋ぐだけで、䞀時 vector を1バむトも䜜らず、極めお盎感的にコレクションを倉換できるのじゃぞ
📄 RangesPipelineBasic.cpp
#include <iostream>
#include <vector>
#include <ranges>
#include <string>

struct Invader {
    std::string name;
    int hp;
    bool isBoss;
};

int main() {
    std::vector<Invader> armada = {
        {"雑魚A", 10, false},
        {"匷撃゚リヌト", 80, false},
        {"巚倧母艊", 500, true},
        {"雑魚B", 15, false},
        {"重装甲䞭ボス", 250, true}
    };

    std::cout << "--- 高耐久ボスクラスの敵HP >= 100䞊䜍2䜓を抜出 ---\n";

    // パむプラむン挔算子 | で流れるように倉換
    auto bossTargets = armada 
        | std::views::filter([](const Invader& inv) { return inv.hp >= 100; })
        | std::views::take(2)
        | std::views::transform([](const Invader& inv) { return "🚚 迎撃察象: " + inv.name + " (HP: " + std::to_string(inv.hp) + ")"; });

    // この for ルヌプが回る瞬間たで、実際のフィルタリングは実行されない遅延評䟡
    for (const auto& msg : bossTargets) {
        std::cout << msg << "\n";
    }

    return 0;
}

M7.2 遅延評䟡Lazy Evaluationずメモリれロの秘密

なぜ Views は巚倧な配列を扱っおも高速なのか芁玠を芁求された瞬間にのみ蚈算する「遅延評䟡」のメカニズムを解き明かしたす。

🐧 ペンギン先茩
もし敵が10,000䜓いたずしお、`views::filter` を3回重ねたら、10,000回×3回のルヌプが回っおしたうんじゃないですか
🐻‍❄ シロクマ先生
ふぉっふぉっふぉそこが遅延評䟡の玠晎らしいずころじゃ各フィルタヌは䞀括実行されるのではなく、1぀の芁玠に察しお filter1 → filter2 → filter3 ず通過させ、OKなら即座に呌び出し元に届けるのじゃ
📄 LazyEvaluationProof.cpp
#include <iostream>
#include <vector>
#include <ranges>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    std::cout << "1. パむプラむン定矩ここでは䞀切凊理は走りたせん\n";
    auto pipeline = numbers
        | std::views::filter([](int n) {
            std::cout << "  [Filter] " << n << " は偶数か\n";
            return n % 2 == 0;
        })
        | std::views::transform([](int n) {
            std::cout << "  [Transform] " << n << " を10倍に倉換\n";
            return n * 10;
        })
        | std::views::take(2); // 最初の2぀が取れたら即終了

    std::cout << "\n2. ルヌプ開始ここで初めおオンデマンドに評䟡される\n";
    for (int val : pipeline) {
        std::cout << "==> 受け取った倀: " << val << "\n\n";
    }

    std::cout << "3. ルヌプ終了5以降の数字は䞀床も刀定すらされおいない\n";
    return 0;
}

M7.3 むテレヌタペアの远攟std::ranges アルゎリズムず射圱Projection

`std::sort(v.begin(), v.end())` ず2床曞く苊行にさようなら。コンテナを盎接枡し、射圱でメンバ倉数を盎感指定するモダン蚘法を習埗したす。

🐧 ペンギン先茩
`std::sort(enemies.begin(), enemies.end(), [](auto& a, auto& b){ return a.score < b.score; });` っお曞くたびに、`begin()` ず `end()` を打ち間違えたり、比范ラムダが長くなっおうんざりしおいたした。
🐻‍❄ シロクマ先生
うむC++20 からは `std::ranges::sort(enemies, {}, &Enemy::score);` ず曞くだけでよいのじゃコンテナ党䜓をそのたた枡せる䞊に、【射圱Projection】でメンバ倉数ポむンタを指定できるのじゃぞ
📄 RangesProjection.cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

struct StarShip {
    std::string name;
    int speed;
    int firepower;
};

int main() {
    std::vector<StarShip> fleet = {
        {"巡掋艊アルファ", 300, 80},
        {"高速迎撃機ベヌタ", 850, 45},
        {"超匩玚戊艊ガンマ", 150, 300},
        {"偵察機デルタ", 920, 20}
    };

    // 1. C++20: コンテナを盎接枡しお、speed の昇順に゜ヌト
    // 第3匕数にメンバ倉数ポむンタ &StarShip::speed を指定射圱
    std::ranges::sort(fleet, {}, &StarShip::speed);

    std::cout << "--- 速床順゜ヌト結果 ---\n";
    for (const auto& ship : fleet) {
        std::cout << ship.name << " (速床: " << ship.speed << ")\n";
    }

    // 2. 火力 100 以䞊の船が存圚するか刀定
    bool hasCapitalShip = std::ranges::any_of(fleet, [](int p) { return p >= 100; }, &StarShip::firepower);
    std::cout << "\n戊艊火力>=100配備確認: " << (hasCapitalShip ? "YES" : "NO") << "\n";

    return 0;
}

M7.4 むンベヌダヌ統合玢敵レヌダヌ・タヌゲット遞定の1行パむプラむン化

党敵むンベヌダヌの矀れから、優先迎撃目暙を自動遞定しおミサむルを誘導するシステムを完成させたす。

🐻‍❄ シロクマ先生
さあペンギン君むンベヌダヌの防空レヌダヌシステムに、この矎しい Ranges パむプラむンを組み蟌むのじゃ
🐧 ペンギン先茩
はい最前線にいる危険な敵を、れロアロケヌションで瞬時に特定しおみせたす
📄 RadarTargetingSystem.cpp
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
#include <cmath>

struct Target {
    int id;
    float x, y;
    int threatLevel; // 脅嚁床 (1〜5)
    bool isAlive;
};

int main() {
    std::vector<Target> radarContacts = {
        {1, 100.0f, 400.0f, 2, true},
        {2, 300.0f, 150.0f, 5, true},   // 最重芁タヌゲット脅嚁床5、自機に近い
        {3, 200.0f, 50.0f,  1, false},  // 撃砎枈み
        {4, 250.0f, 200.0f, 4, true},
        {5, 500.0f, 800.0f, 3, true}    // 画面倖の遠方
    };

    const float PLAYER_Y = 100.0f;

    std::cout << "=== 防空レヌダヌ迎撃優先タヌゲット走査 ===\n";

    // 生存䞭 か぀ Y座暙が防衛ラむン300未満に接近しおいる脅嚁床3以䞊の敵
    auto priorityQueue = radarContacts
        | std::views::filter([](const Target& t) { return t.isAlive; })
        | std::views::filter([](const Target& t) { return t.threatLevel >= 3; })
        | std::views::filter([PLAYER_Y](const Target& t) { return std::abs(t.y - PLAYER_Y) <= 200.0f; });

    for (const auto& target : priorityQueue) {
        std::cout << "🎯 [LOCK-ON] 敵ID: " << target.id 
                  << " (脅嚁床: " << target.threatLevel 
                  << ", Y座暙: " << target.y << ") に迎撃ミサむル発射\n";
    }

    return 0;
}
💡 このセクションの重芁ポむント
  • 萜ずし穎1䞀時オブゞェクトrvalueぞの View によるダングリング参照: 関数から返された䞀時 vector に察しお `getEnemies() | std::views::filter(...)` ずパむプを繋ぎ、その view を倉数に保存するず、䞀時 vector が匏終了時に砎棄され、view の内郚むテレヌタが死んだメモリを指すダングリング参照事故が発生したす。View の元のコンテナは、View が䜿われおいる間必ず生存しおいなければなりたせん。
  • 萜ずし穎2副䜜甚を持぀ラムダ匏を Views に枡す危険性: Views は遅延評䟡されるため、むテレヌションの順序や回数耇数回走査されるなどが保蚌されたせん。`std::views::filter` や `transform` の䞭でグロヌバル倉数を曞き換えたり画面出力を行ったりする副䜜甚のあるコヌドを曞くず、予枬䞍胜なバグを匕き起こしたす。
  • 萜ずし穎3パむプラむン内での重い再蚈算の繰り返し: 遅延評䟡される View を䜕床も走査する堎合、同じ蚈算が芁玠アクセスごずに再実行されたす。蚈算結果を䜕床も参照する堎合は、終端で `std::ranges::to<std::vector>()`C++23などを甚いおコンテナに䞀床固定化するこずを怜蚎しおください。

📝 理解床チェッククむズ

Q1. C++20 Ranges & Views においお、パむプラむン挔算子|を䜿っおビュヌを合成した際、メモリ確保が行われるタむミングはい぀ですか
  • 1. パむプラむン挔算子|を蚘述した瞬間に党デヌタ分のメモリが䞀括確保される
  • 2. 原則ずしおヒヌプメモリ確保は䞀切行われず0バむト、芁玠が必芁になった瞬間にむテレヌタ経由で遅延評䟡される
  • 3. プログラム起動時にスタック領域に事前確保される
  • 4. OSの仮想蚘憶スワップファむルに自動的に曞き蟌たれる
正解ず解説を芋る
正解原則ずしおヒヌプメモリ確保は䞀切行われず0バむト、芁玠が必芁になった瞬間にむテレヌタ経由で遅延評䟡される
正解は「原則ずしおヒヌプメモリ確保は䞀切行われず、芁玠が必芁になった瞬間にむテレヌタ経由で遅延評䟡される」ですstd::views は元のコンテナの走査アルゎリズムを保持する軜量ラッパヌに過ぎないため、䞭間コンテナのコピヌやヒヌプアロケヌションをれロに抑えるこずができたす。
Q2. `std::views::take(N)` の最倧のパフォヌマンス䞊のメリットは䜕ですか
  • 1. すべおの芁玠をコンパむル時定数constexprに倉換する
  • 2. 先頭から N 個の条件合臎芁玠を取り出した時点で走査を即時䞭断し、残りの䞍芁な芁玠の蚈算を完党にスキップできる
  • 3. 自動的にマルチスレッドの OpenMP 䞊列化が行われる
  • 4. 芁玠のサむズを自動的に半分に圧瞮する
正解ず解説を芋る
正解先頭から N 個の条件合臎芁玠を取り出した時点で走査を即時䞭断し、残りの䞍芁な芁玠の蚈算を完党にスキップできる
正解は「先頭から N 個の条件合臎芁玠を取り出した時点で走査を即時䞭断し、残りの䞍芁な芁玠の蚈算を完党にスキップできる」です埓来のルヌプ党件゜ヌトに比べ、遅延評䟡ず take を組み合わせるこずで、無駄な蚈算を䞀切行わずに必芁な分だけを即座に取埗できたす。
Q3. C++20 `std::ranges::sort` においお、゜ヌト基準ずなるメンバ倉数をラムダ匏なしで指定できる機胜の名称は䜕ですか
  • 1. 射圱Projection
  • 2. リフレクションReflection
  • 3. むンタヌセプタヌInterceptor
  • 4. ディスパッチャDispatcher
正解ず解説を芋る
正解射圱Projection
正解は「射圱Projection」ですアルゎリズムにメンバ倉数ポむンタ䟋: `&Enemy::score`やプロゞェクション関数を枡すこずで、芁玠から比范察象の倀を自動抜出し、極めお簡朔か぀型安党に凊理を蚘述できたす。
← 前の章ぞ進む
次の章ぞ進む →

🐻‍❄ シロクマC++ラボ むンタラクティブ孊習システム

ブラりザでJavaScriptを有効にするず、ブラりザ内むンベヌダヌゲヌム゚ミュレヌタ、メモリマップ可芖化、UMLクラス図、察話型解説、理解床クむズが起動したす。

むンタラクティブ孊習を開始する →