C++: 関数オブジェクトとラムダ

レッスン37では、イテレータについて学びました。

ここでは、STLアルゴリズムの「秘密兵器」——関数オブジェクトについて学びます。

アルゴリズムに、関数オブジェクト。これが、STLの力です。


1. 関数オブジェクトとは

(1) 1.1 関数オブジェクトとは?

関数オブジェクト(Functor)とは、関数呼び出し演算子を持つオブジェクトです。

関数オブジェクトの種類:

  1. 関数ポインタ
  2. 関数オブジェクトクラスoperator() をオーバーロード)
  3. ラムダ式(C++11)

(2) 1.2 なぜ関数オブジェクトが必要?

STLアルゴリズムは比較演算子を使用するものが多いですが、デフォルトの動作をカスタマイズしたい場合があります。関数オブジェクトで独自のルールを定義できます。

たとえ話:




2. 関数ポインタ

(1) 2.1 基本的な使い方

▶ サンプル 2:STLコンテナの使用(難易度 ⭐)

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

// 降順比較関数
bool compareDesc(int a, int b) {
    return a > b; // 大きい方を前に
}

int main() {
    std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
    
    // 関数ポインタでソート
    std::sort(v.begin(), v.end(), compareDesc);
    
    for (int x : v) {
        std::cout << x << " ";
    }
    std::cout << std::endl;
    
    return 0;
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
9 6 5 4 3 2 2 1 1 
💡 ヒント:

  • 関数ポインタはC言語由来の方法で、C++では関数オブジェクトやラムダの使用が推奨されます



3. 関数オブジェクトクラス

(1) 3.1 関数オブジェクトクラスとは?

関数オブジェクトクラスとは、operator() をオーバーロードしたクラスで、インスタンスを関数のように呼び出せます。

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

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

// 文字列長で比較する関数オブジェクト
struct CompareByLength {
    bool operator()(const std::string& a, const std::string& b) const {
        return a.length() < b.length();
    }
};

int main() {
    std::vector<std::string> words = {"apple", "banana", "cat", "dog"};
    
    // 文字列長でソート
    std::sort(words.begin(), words.end(), CompareByLength());
    
    for (const auto& w : words) {
        std::cout << w << " ";
    }
    std::cout << std::endl;
    
    return 0;
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
cat dog apple banana 

(2) 3.2 関数オブジェクトの利点

特徴 関数ポインタ 関数オブジェクトクラス
状態保持 不可 可能(メンバ変数)
パフォーマンス インライン化困難 インライン化可能、高速
柔軟性 低い 高い(テンプレート)

(3) 3.3 状態を持つ関数オブジェクト

▶ サンプル:状態付き関数オブジェクト(難易度 ⭐⭐⭐)

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

// 閾値を保持する関数オブジェクト
struct Counter {
    int threshold; // 閾値(状態)
    
    Counter(int t) : threshold(t) {}
    
    bool operator()(int x) const {
        return x > threshold; // 閾値と比較
    }
};

int main() {
    std::vector<int> v = {1, 5, 10, 15, 20};
    
    // 閾値10以上をカウント
    Counter counter(10);
    
    // 10より大きい要素を数える
    int count = std::count_if(v.begin(), v.end(), counter);
    
    std::cout << "10より大きい要素数:" << count << std::endl; // 出力:2
    
    return 0;
}
▶ 試してみよう


4. ラムダ式

(1) 4.1 ラムダ式とは?

ラムダ式はC++11で導入されたインライン関数で、関数の中で直接定義できます。

基本構文:

CPP
[キャプチャ](パラメータ) -> 戻り値型 { 本体 }
部分 説明
キャプチャ 外部変数のリスト(取り込む変数)
パラメータ 引数リスト
戻り値型 戻り値の型(省略可能)
本体 関数本体

(2) 4.2 基本的な例

▶ サンプル:ラムダでソート(難易度 ⭐)

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

int main() {
    std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
    
    // ラムダで降順ソート
    std::sort(v.begin(), v.end(), [](int a, int b) {
        return a > b;
    });
    
    for (int x : v) {
        std::cout << x << " ";
    }
    std::cout << std::endl;
    
    return 0;
}
▶ 試してみよう

(3) 4.3 キャプチャリスト

キャプチャリストはラムダ内で外部の変数を使用する方法です。

構文 説明
[] なにもキャプチャしない
[x] xを値でキャプチャ
[&x] xを参照でキャプチャ
[=] すべての変数を値でキャプチャ
[&] すべての変数を参照でキャプチャ
[this] thisポインタをキャプチャ(クラス内で使用)

▶ サンプル:キャプチャ付きラムダ(難易度 ⭐⭐)

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

int main() {
    std::vector<int> v = {1, 5, 10, 15, 20};
    int threshold = 10;
    
    // 閾値をキャプチャしてカウント
    int count = std::count_if(v.begin(), v.end(),
        [threshold](int x) {
            return x > threshold;
        });
    
    std::cout << threshold << "より大きい要素数:" << count << std::endl;
    
    return 0;
}
▶ 試してみよう


5. STL定義の関数オブジェクト

(1) 5.1 算術関数オブジェクト

<functional> ヘッダで定義されている関数オブジェクト:

関数オブジェクト 機能
std::plus<T> 加算 std::plus<int>()
std::minus<T> 減算 std::minus<int>()
std::multiplies<T> 乗算 std::multiplies<int>()
std::divides<T> 除算 std::divides<int>()
std::negate<T> 符号反転 std::negate<int>()

▶ サンプル:multipliesの使用(難易度 ⭐)

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

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    
    // 各要素を2倍に変換
    std::transform(v.begin(), v.end(), v.begin(),
        std::bind(std::multiplies<int>(), std::placeholders::_1, 2));
    
    for (int x : v) {
        std::cout << x << " ";
    }
    std::cout << std::endl;
    
    return 0;
}
▶ 試してみよう

(2) 5.2 比較関数オブジェクト

関数オブジェクト 機能
std::equal_to<T> 等価
std::not_equal_to<T> 不等価
std::greater<T> 大なり
std::less<T> 小なり
std::greater_equal<T> 以上
std::less_equal<T> 以下

(3) 5.3 論理関数オブジェクト

関数オブジェクト 機能
std::logical_and<T> 論理積
std::logical_or<T> 論理和
std::logical_not<T> 論理否定



6. 総合例

▶ サンプル 1:成績処理システム(難易度 ⭐⭐⭐)

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

struct Student {
    std::string name;
    int score;
};

int main() {
    std::vector<Student> students = {
        {"田中", 85},
        {"佐藤", 92},
        {"鈴木", 78}
    };
    
    // 1. 点数でソート
    std::sort(students.begin(), students.end(),
        [](const Student& a, const Student& b) {
            return a.score > b.score;
        });
    
    // 2. 最高点の生徒を見つける
    auto max_it = std::max_element(students.begin(), students.end(),
        [](const Student& a, const Student& b) {
            return a.score < b.score;
        });
    
    std::cout << "最高点:" << max_it->name << " " << max_it->score << std::endl;
    
    // 3. 及格者数をカウント
    int passed = std::count_if(students.begin(), students.end(),
        [](const Student& s) {
            return s.score >= 60;
        });
    
    std::cout << "及格者数:" << passed << std::endl;
    
    return 0;
}
▶ 試してみよう

▶ サンプル 3:状態を持つ関数オブジェクト(難易度 ⭐⭐)

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

// 状態を保持する関数オブジェクト
class ThresholdCounter {
private:
    int threshold;
    int count;

public:
    ThresholdCounter(int t) : threshold(t), count(0) {}

    void operator()(int value) {
        if (value > threshold) {
            count++;
        }
    }

    int getCount() const { return count; }
};

int main() {
    std::vector<int> data = {10, 25, 5, 30, 15, 40, 8};
    ThresholdCounter counter(20);

    // 閾値以上の値をカウント
    std::for_each(data.begin(), data.end(), std::ref(counter));

    std::cout << "20より大きい値の数: " << counter.getCount() << std::endl;

    return 0;
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
20より大きい値の数: 3

❓ よくある質問

Q:ラムダと関数オブジェクトクラスの使い分けは? A:- 単純・一時的 → ラムダ(コードが簡潔)

  • 再利用・状態保持 → 関数オブジェクトクラス(正式な定義)

Q:auto でラムダ型を宣言できる? A:ラムダは固有の型を持つため、auto を使うのが一般的で、型を明示的に書く必要はありません。

CPP
auto func = [](int x) { return x * 2; };
// std::function<int(int)> func = ... // も可能

Q:いつ std::function を使う? A:関数オブジェクトを変数に保存する場合(メンバ変数、戻り値など)、std::function を使います。


📖 まとめ

トピック 要点
関数ポインタ 単純だが柔軟性に欠ける
関数オブジェクトクラス 状態を保持可能、パフォーマンスが良い
ラムダ式 インラインで簡潔な関数定義
定義済み関数オブジェクト std::plus など、<functional> で提供
キャプチャリスト ラムダ内で外部変数を使用

📝 練習問題

  1. 初級(難易度 ⭐): 関数オブジェクトクラス(operator() をオーバーロードしたクラス)を作成し、「降順」比較を実装してください。std::sort でテストしてください。

  2. 中級(難易度 ⭐⭐): std::function 型を使って、様々な呼び出し可能オブジェクト(関数、ラムダ、関数オブジェクト)を保存し、呼び出してください。

  3. 上級(難易度 ⭐⭐⭐): std::bind を使って引数をバインドし、新しい呼び出し可能オブジェクトを作成してください。「部分適用」の関数アダプタを実装してください。



次のレッスン:STLアダプタ(#39)

Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%