はじめに
C++には、「呼び出し可能 (callable)」なものがいくつかあります。
- 通常の関数
- ラムダ式
()
演算子をオーバーロードした関数オブジェクト(ファンクタ)
これらはそれぞれ型が異なるため、通常は同じ変数に代入することはできません。しかし、<functional>
ヘッダーで提供される std::function
を使えば、これらの異なる種類の「呼び出し可能なもの」を、統一された一つの型として扱うことができます。
std::function
は、後から実行する処理を切り替えたり、コールバック関数を登録したりする場面で、非常に強力なツールとなります。
【前提】C++11とは?
C++11は、2011年に正式化されたC++言語のメジャーアップデート版です。std::function
やラムダ式はC++11で導入されたため、利用するにはC++11以降に対応したコンパイラが必要です。
std::function
を使ったサンプルコード
このコードは、同じシグネチャ(int
を2つ受け取りint
を返す)を持つ、3種類の異なる「呼び出し可能なもの」を、全て std::function
型の変数に代入して実行します。
完成コード
#include <iostream>
#include <functional> // std::function
#include <string>
using namespace std;
// 1. 関数オブジェクト(ファンクタ)
struct Adder {
int operator()(int a, int b) const {
return a + b;
}
};
// 2. 通常の関数
int multiplier(int a, int b) {
return a * b;
}
int main() {
// std::functionの型を定義: <戻り値の型(引数の型1, 引数の型2)>
function<int(int, int)> operation;
// --- a. 関数オブジェクトを代入 ---
operation = Adder{};
cout << "関数オブジェクトの結果: " << operation(10, 5) << endl;
// --- b. 通常の関数を代入 ---
operation = multiplier;
cout << "通常の関数の結果: " << operation(10, 5) << endl;
// --- c. ラムダ式を代入 ---
operation = [](int a, int b) {
return a - b;
};
cout << "ラムダ式の結果: " << operation(10, 5) << endl;
return 0;
}
実行結果
関数オブジェクトの結果: 15
通常の関数の結果: 50
ラムダ式の結果: 5
コードの解説
function<int(int, int)> operation;
これが std::function
の宣言です。
std::function< ... >
:< >
の中に、保持したい関数のシグネチャ(戻り値と引数の型)を記述します。int(int, int)
: これは「int
型の引数を2つ取り、int
型の値を返す」というシグネチャを表しています。
この operation
という変数には、上記のシグネチャに一致するものであれば、関数オブジェクト、通常の関数、ラムダ式のいずれでも代入することが可能になります。
operation = Adder{};
Adder
構造体は、operator()
を実装しているため「関数オブジェクト」として振る舞います。そのシグネ-チャが int(int, int)
と一致するため、operation
に代入できます。
operation = multiplier;
multiplier
は、シグネチャが一致する通常の関数です。これも operation
に代入できます。
operation = [](int a, int b) { ... };
ラムダ式も、シグネチャが一致すれば operation
に代入できます。
operation(10, 5)
std::function
変数は、まるで本物の関数であるかのように、()
を使って中の処理を呼び出すことができます。
まとめ
今回は、C++11の std::function
を使って、様々な「呼び出し可能なもの」を統一的に扱う方法を解説しました。
std::function<戻り値(引数...)>
で、関数のシグネチャを指定して変数を宣言する。- 宣言した変数には、シグネチャが一致する関数、関数オブジェクト、ラムダ式などを代入できる。
std::function
は、実行時に処理内容を動的に切り替えたい場合や、様々な種類のコールバック関数を受け入れる必要がある場合に、プログラムの柔軟性を大幅に向上させます。