【C++】関数オブジェクト(ファンクタ)とは?()演算子オーバーロードの使い方

目次

はじめに

C++のstd::sortのような標準アルゴリズムは、ソートの順序などをカスタマイズするために、比較ルールを引数として受け取ることができます。この「ルール」を渡す方法の一つが、**関数オブジェクト(ファンクタ)**です。

関数オブジェクトは、()演算子をオーバーロードしたクラスや構造体です。これにより、そのクラスから生成したオブジェクトを、my_object(arg1, arg2) のように、あたかも関数であるかのように呼び出すことができます。

状態(メンバ変数)を保持できるという点で、通常の関数ポインタよりも柔軟な処理を記述できます。この記事では、std::sortの比較ルールを関数オブジェクトで定義する、最も代表的な使い方を解説します。


関数オブジェクトを使ったサンプルコード

このコードは、Product(商品)のvectorを、「ID順(昇順)」と「価格順(降順)」という2つの異なるルールでソートします。それぞれのルールを、個別の関数オブジェクトとして定義します。

完成コード

#include <iostream>
#include <vector>
#include <string>
#include <algorithm> // sort

using namespace std;

// ソート対象のデータ構造
struct Product {
    int id;
    string name;
    int price;
};

// --- 1. IDで比較するための関数オブジェクト ---
struct CompareById {
    // ()演算子をオーバーロード
    bool operator()(const Product& a, const Product& b) const {
        return a.id < b.id; // IDの昇順で比較
    }
};

// --- 2. 価格で比較するための関数オブジェクト ---
struct CompareByPriceDescending {
    bool operator()(const Product& a, const Product& b) const {
        return a.price > b.price; // 価格の降順で比較
    }
};

int main() {
    vector<Product> products = {
        {102, "みかん", 80},
        {101, "りんご", 120},
        {103, "ぶどう", 250}
    };
    
    // --- ID順でソート ---
    // CompareByIdのオブジェクトをsortの第3引数に渡す
    sort(products.begin(), products.end(), CompareById{});
    
    cout << "--- ID順ソート結果 ---" << endl;
    for (const auto& p : products) {
        cout << "ID:" << p.id << ", 価格:" << p.price << endl;
    }

    // --- 価格の降順でソート ---
    // CompareByPriceDescendingのオブジェクトをsortの第3引数に渡す
    sort(products.begin(), products.end(), CompareByPriceDescending{});

    cout << "\n--- 価格順(降順)ソート結果 ---" << endl;
    for (const auto& p : products) {
        cout << "ID:" << p.id << ", 価格:" << p.price << endl;
    }
    
    return 0;
}

コードの解説

struct CompareById { ... };

これが関数オブジェクト(ファンクタ)です。

  • bool operator()(const Product& a, const Product& b) const: このクラスの関数呼び出し演算子 () をオーバーロードしています。std::sortは、比較が必要になるたびに、この () 演算子を2つのProductオブジェクトを引数として呼び出します。
  • constメンバ関数にすることで、このオブジェクトが状態を変更しないことを示しています。
  • 戻り値: ab よりも「前に来るべき」場合に true を返すように実装します。

sort(products.begin(), products.end(), CompareById{});

std::sortの第3引数には、比較処理を行う「呼び出し可能オブジェクト」を渡すことができます。

  • CompareById{}: ここで、CompareByIdクラスの一時的なオブジェクトを生成し、sort関数に渡しています。sortアルゴリズムは、内部でこのオブジェクトの()演算子を繰り返し呼び出して、要素の順序を決定します。

ラムダ式との関係

C++11以降では、このような単純な関数オブジェクトは、ラムダ式でより簡潔に記述できます。

// CompareById と同じ意味のラムダ式
sort(products.begin(), products.end(), [](const Product& a, const Product& b){
    return a.id < b.id;
});

ラムダ式は、実はコンパイラが裏側で、この例のようなユニークな名前を持つ関数オブジェクトを自動生成している、と考えることができます。


まとめ

今回は、関数のように振る舞うオブジェクトである「関数オブジェクト(ファンクタ)」について解説しました。

  • ()演算子をオーバーロードしたクラスや構造体のこと。
  • 状態(メンバ変数)を保持できるため、単純な関数ポインタよりも柔軟。
  • std::sort などの標準アルゴリズムに、カスタムの振る舞いを渡すための強力な手段。

ラムダ式が主流となった現代C++では、自前で単純な関数オブジェクトを定義する機会は減りましたが、その仕組みを理解しておくことは、C++のSTL(標準テンプレートライブラリ)や、より高度なプログラミングを深く理解する上で非常に重要です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次