C++: コンテナアダプタ

レッスン38では、関数オブジェクトについて学びました。

ここでは、関数アダプタについて学びます——既存の関数を「改造」して必要な形に変換します。

レゴブロックのように、小さな部品を組み合わせて大きな作品を作ります。


1. アダプタの概要

(1) 1.1 アダプタとは?

アダプタ(Adapter)とは、関数オブジェクトの振る舞いを変更するテンプレートです。

主なアダプタ:




2. std::bind——引数のバインド

(1) 2.1 基本的な使い方

std::bind関数の引数をバインドし、新しい関数オブジェクトを作成します。

▶ サンプル 2:コード例(難易度 ⭐)

CPP
#include <iostream>
#include <functional>

int add(int a, int b) {
    return a + b;
}

int main() {
    // addの第一引数を10にバインド
    auto add10 = std::bind(add, 10, std::placeholders::_1);
    
    std::cout << "add10(5) = " << add10(5) << std::endl; // 出力:15
    std::cout << "add10(20) = " << add10(20) << std::endl; // 出力:30
    
    return 0;
}
▶ 試してみよう
💡 ヒント:

  • std::placeholders::_1 は「後で埋める第一引数」を表す

(2) 2.2 引数の順序を変更

▶ サンプル:引数順序の入れ替え(難易度 ⭐⭐)

CPP
#include <iostream>
#include <functional>

int subtract(int a, int b) {
    return a - b;
}

int main() {
    // 引数の順序を入れ替え
    auto reverse_subtract = std::bind(subtract,
        std::placeholders::_2,
        std::placeholders::_1);
    
    std::cout << "subtract(10, 3) = " << subtract(10, 3) << std::endl; // 7
    std::cout << "reverse(10, 3) = " << reverse_subtract(10, 3) << std::endl; // -7
    
    return 0;
}
▶ 試してみよう


3. std::ref——参照渡し

(1) 3.1 問題:値渡しによる状態消失

デフォルトでは、STLアルゴリズムは値渡しで関数オブジェクトをコピーするため、状態が共有されません。

▶ サンプル:std::refで解決(難易度 ⭐⭐)

CPP
#include <iostream>
#include <algorithm>
#include <vector>
#include <functional>

struct Counter {
    int count = 0;
    void operator()(int) { count++; }
};

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    Counter counter;
    
    // ❌ エラー!値渡しなので、counterの状態は更新されない
    std::for_each(v.begin(), v.end(), counter);
    std::cout << "値渡し後:" << counter.count << std::endl; // 出力:0
    
    // ✅ 正しい!std::refで参照渡し
    std::for_each(v.begin(), v.end(), std::ref(counter));
    std::cout << "参照渡し後:" << counter.count << std::endl; // 出力:5
    
    return 0;
}
▶ 試してみよう


4. std::not_fn——論理否定

(1) 4.1 基本的な使い方

std::not_fn関数オブジェクトの戻り値を否定します。

▶ サンプル:述語の否定(難易度 ⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    
    // 最初の偶数を見つける
    auto it1 = std::find_if(v.begin(), v.end(),
        [](int x) { return x % 2 == 0; });
    std::cout << "最初の偶数:" << *it1 << std::endl; // 2
    
    // 最初の奇数を見つける(否定)
    auto it2 = std::find_if(v.begin(), v.end(),
        std::not_fn([](int x) { return x % 2 == 0; }));
    std::cout << "最初の奇数:" << *it2 << std::endl; // 1
    
    return 0;
}
▶ 試してみよう


5. std::mem_fn——メンバ関数ポインタ

(1) 5.1 問題:メンバ関数ポインタの複雑さ

メンバ関数ポインタは構文が複雑ですが、std::mem_fn を使うと簡潔になります。

▶ サンプル:メンバ関数の呼び出し(難易度 ⭐⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>

struct Student {
    std::string name;
    void display() const {
        std::cout << "Student: " << name << std::endl;
    }
};

int main() {
    std::vector<Student> students = {{"田中"}, {"佐藤"}};
    
    // std::mem_fnでメンバ関数を呼び出し
    std::for_each(students.begin(), students.end(),
        std::mem_fn(&Student::display));
    
    return 0;
}
▶ 試してみよう


6. 総合例

▶ サンプル 1:柔軟な成績処理(難易度 ⭐⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

int main() {
    std::vector<int> scores = {85, 92, 78, 90, 88};
    int threshold = 90;
    
    // 閾値以上のスコアをカウント
    int count = std::count_if(scores.begin(), scores.end(),
        std::bind(std::greater_equal<int>(),
            std::placeholders::_1,
            threshold));
    
    std::cout << threshold << "点以上の人数:" << count << std::endl;
    
    return 0;
}
▶ 試してみよう

▶ サンプル 3:std::bindでカスタム比較(難易度 ⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

int main() {
    std::vector<int> values = {15, 8, 23, 4, 16, 42, 7};

    // std::bindを使って20より大きい最初の値を見つける
    auto it = std::find_if(values.begin(), values.end(),
        std::bind(std::greater<int>(), std::placeholders::_1, 20));

    if (it != values.end()) {
        std::cout << "20より大きい最初の値: " << *it << std::endl;
    }

    return 0;
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
20より大きい最初の値: 23

❓ よくある質問

Q:C++11以降もstd::bindは必要? A:はい、しかしラムダの使用が推奨されます。ラムダの方が簡潔で、パフォーマンスも良いです。

CPP
// std::bind
auto f1 = std::bind(add, 10, std::placeholders::_1);

// ラムダ(推奨)
auto f2 = [](int x) { return add(10, x); };

Q:いつアダプタを使う? A:既存の関数がほぼ要件を満たすが、引数が合わない場合。


Q:priority_queueで最小ヒープを作るには? A:std::greater<T>を第2テンプレート引数に指定します:

CPP
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;

Q: STLアダプタについて最も重要なことは何ですか? A: まず基本概念を理解し、それから実践的な例で練習することです。

📖 まとめ

アダプタ 機能
std::bind 引数のバインド
std::ref 参照渡し
std::not_fn 論理否定
std::mem_fn メンバ関数ポインタ

📝 練習問題

  1. 初級(難易度 ⭐): stack<int> を作成し、1、2、3を順にプッシュし、ループでポップしてすべての要素を出力してください。出力順序を観察してください。

  2. 中級(難易度 ⭐⭐): queue を使って「印刷ジョブキュー」を実装してください——複数の印刷ジョブが順番に処理されるのをシミュレートし、各ジョブ処理後に残りのキューサイズを出力してください。

  3. 上級(難易度 ⭐⭐⭐): priority_queue を使って「タスクスケジューラ」を実装してください——各タスクは優先度(1〜10)を持ち、優先度が高い順に処理し、同じ優先度なら挿入順で処理します。



次のレッスン:例外処理(#40)

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%