はじめに
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
メンバ関数にすることで、このオブジェクトが状態を変更しないことを示しています。- 戻り値:
a
がb
よりも「前に来るべき」場合に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(標準テンプレートライブラリ)や、より高度なプログラミングを深く理解する上で非常に重要です。