std::function
やテンプレート、関数ポインタを活用する複数の方法を紹介します。後半ではそれぞれの方法のメリットとデメリット、注意点等を説明します。
std::function
std::function
クラスのテンプレート引数に、引数として渡したい関数のシグネチャの一部(引数型と戻り値型)を記述します(例: int(int)
)。
int call(std::function<int(int)> fn) {
return fn(3);
}
auto fn = [](int v) { return v * 2; };
call(fn); // 6
// int gn(int v) { return v * 3; };
call(gn); // 9
// #include <ctype.h> // int isalpha(int c);
call(isalpha); // 0
テンプレート
template<class Fn> int call(Fn fn) {
return fn(3);
}
auto fn = [](int v) { return v * 2; };
call(fn); // 6
関数呼び出しが行えるものであればどんな関数オブジェクトでも渡せます(ラムダ、ファンクタ、Blocks等)。
関数ポインタ
C言語由来の関数ポインタを活用する方法もあります。
int call(int(*fn)(int)) {
return fn(3);
}
int fn(int v) { return v * 2; }
int main() {
call(fn); // 6
}
キャプチャー無しのラムダ式は関数ポインタとして渡すことも可能です。
call([](int v) { return v * 2; }); // 6
AppleのC言語拡張「ブロック(Blocks)」には対応していません。
call(^(int v) { return v * 2; }); // No matching function for call to 'call'
どれを使うべきか
受け取る関数の型が事前に決まっているような場合は# std::functionを使うと良いでしょう。型が明確になるため、厳格かつ読みやすいコードにもなります。
関数ポインタ
# 関数ポインタによる方法は用途が限られます。C言語との連携を意識する際に使うと良いでしょう。ラムダ式利用時はキャプチャーが行えないという制限があるため注意して採用する必要があります。std::functionとテンプレートを用いた方法であればキャプチャーは可能です。
テンプレート
# テンプレートを用いた方法はより汎用的なコード書く際に使われます。マクロの代替として活用するのも良いでしょう。foreach文のような機能はテンプレートを用いて実装するのがオススメです。
ただしテンプレートを用いた方法では、受け取った関数の型の数だけテンプレート関数の実体化が行われてしまう点に注意が必要です。
call([](int v) { return 1; });
call([](double v) { return 1; });
この場合、テンプレート関数callはint版とdouble版の二つの実体を生成します。テンプレート関数の用途や処理の規模によっては注意が必要になります。
// イメージ
int _call(lambda<int(int)> obj) { return obj.operator(int)(3); }
int _call(lambda<int(double) obj>) { return obj.operator(double)(3); }
ただし、比較的小さな関数であれば、std::function活用時よりもコンパイルスピードや実行時のパフォーマンスが向上する場合があります。処理のインライン化が行われやすいためです。コンパイルスピードや実行スピードの改善が必要になった際には両者の使い分けを検討してみると良いでしょう。
std::function
std::functionには複雑な設計が用いられており、コンパイル時や実行時のコストが大きくなりやすいという欠点があります。対象の関数は動的確保された領域に保持され、仮想関数テーブルによって管理されることがあります。