関数オブジェクトは、括弧演算子をメンバー関数として定義するクラスのインスタンスです。関数オブジェクトは関数として使用され、関数が呼び出されると、必ず括弧演算子が呼び出されます。次のクラス定義を考えます。
class biggerThanThree { public: bool operator () (int val) { return val > 3; } };
クラスのインスタンス biggerThanThree を作成すると、関数呼び出し構文を使用してこのオブジェクトを参照するたびに、括弧演算子のメンバー関数が呼び出されます。このクラスを汎用化するには、コンストラクタと、コンストラクタによって設定される定数データフィールドを追加します。
class biggerThan { public: const int testValue; biggerThan (int x) : testValue(x) { } bool operator () (int val) { return val > testValue; } };
結果は汎用 biggerthanX 関数となり、X の値はクラスのインスタンスを作成するときに決定されます。たとえば、述語を要求する汎用関数の 1 つへの引数として、これを実行することができます。この方法を使用する次のコードでは、リストで 12 より大きい最初の値が検出されます。
list<int>::iterator firstBig = find_if (aList.begin(), aList.end(), biggerThan(12));
関数の代わりに関数オブジェクトを使用するほうが便利な場合があります。新しい関数の代わりに標準 C++ ライブラリが提供する既存の関数オブジェクトを使用したり、インライン関数呼び出しを使用して実行を向上させたり、関数オブジェクトを使用してオブジェクトが保持する状態情報へアクセスしたり、設定することができます。以下の 3 つの節で、上記について説明します。
表 5 に、標準 C++ ライブラリが提供する関数オブジェクトを示します。
表 5 -- 標準 C++ ライブラリが提供する関数オブジェクト
関数オブジェクト | 実行される演算 |
---|---|
算術関数 |
|
plus |
加算 x + y |
minus |
減算 x - y |
multiplies |
乗算 x * y |
divides |
除算 x / y |
modulus |
剰余 x % y |
negate |
論理否定 - x |
比較関数 |
|
equal_to |
等価テスト x == y |
not_equal_to |
非等価テスト x != y |
greater |
大なり比較 x > y |
less |
小なり比較 x < y |
greater_equal |
以上比較 x >= y |
less_equal |
以下比較 x <= y |
論理関数 |
|
logical_and |
論理結合 x && y |
logical_or |
論理和 x || y |
logical_not |
論理否定 ! x |
上記の演算の実行例を以下に示します。ます、最初の例では、plus() を使用して、整数値の 2 つのリストの要素ごとの加算を行い、結果を最初のリストに戻します。これは、次のコードで実行することができます。
transform (listOne.begin(), listOne.end(), listTwo.begin(), listOne.begin(), plus<int>() );
2 番目の例では、ブール値のベクトルの各要素を否定します。
transform (aVec.begin(), aVec.end(), aVec.begin(), logical_not<bool>() );
表 5 の関数を定義するために標準 C++ ライブラリで使用される基底クラスは、新しい単項または 2 項の関数オブジェクトの作成にも利用することができます。unary_function と binary_function のクラス定義は、#including functional で組み込むことができます。
基底クラスは次のように定義されます。
template <class Arg, class Result> struct unary_function { typedef Arg argument_type; typedef Result result_type; }; template <class Arg1, class Arg2, class Result> struct binary_function { typedef Arg1 first_argument_type; typedef Arg2 second_argument_type; typedef Result result_type; };
このような関数の使用例は、6.3 節に記述されています。この節では、Widget 型の 2 項関数および integer 型の引数を使用し、整数値に対するウィジェット識別番号を比較しています。これを実行する関数は、次のように書かれています。
struct WidgetTester : binary_function<Widget, int, bool> { public: bool operator () (const Widget & wid, int testid) const { return wid.id == testid; } };
関数の代わりに関数オブジェクトを使用する第 2 の理由は、コードの実行速度が向上することです。多くの場合、3.3.2.1 節の transform() の例に示すように、関数呼び出しのオーバーヘッドを排除することで、関数オブジェクトの呼び出しをインラインで拡張することができます。
関数の代わりに関数オブジェクトを使用する第 3 の理由は、関数呼び出しのたびに、以前の呼び出しによる状態設定を再現する必要があるこです。このような状況が発生する例として、汎用アルゴリズム generate() を共に使用してジェネレータを作成する場合があります。ジェネレータは、呼び出されるたびに異なる値を返す単純な関数です。最も一般的に使用されるジェネレータは形式は乱数発生関数ですが、この概念には他の用途もあります。シーケンスジェネレータは単純に自然数 (1, 2, 3, 4 など) の増加シーケンスの値を返します。プログラミング言語 APL で同様の演算の後にこのオブジェクト iotaGen を呼び出して、次のように定義することができます。
class iotaGen { public: iotaGen (int start = 0) : current(start) { } int operator () () { return current++; } private: int current; };
iota オブジェクトは現在値を維持します。値はコンストラクタで設定することも、デフォルトでゼロにすることもできます。関数呼び出し演算子が呼び出されるたびに、現在値が返され、また、増分されます。このオブジェクトを使用すると、次の標準 C++ ライブラリ関数 generate() の呼び出しで、1 から 20 までの値の 20 個の要素のベクトルが初期化されます。
vector<int> aVec(20); generate (aVec.begin(), aVec.end(), iotaGen(1));
関数オブジェクトを使用する複雑な例には基数ソートのプログラム例があり、6.3 節にデータ型リストの使用例として挙げられています。このプログラム参照は関数オブジェクトで初期化されるため、関数オブジェクトの呼び出しシーケンスの途中で、呼び出しプログラムのローカル値にアクセスしたり、変更したりすることができます。