【C++】関数を引数に渡す色々な方法【STL テンプレート 関数ポインタ】


std::functionやテンプレート、関数ポインタを活用する複数の方法を紹介します。後半ではそれぞれの方法のメリットとデメリット、注意点等を説明します。

std::function

std::functionのテンプレート引数に、渡したい関数の型を記述します。

int call(std::function<int(int)> fn) {
  return fn(3);
}
auto fn = [](int v) { return v * 2; };
call(fn); // 6

テンプレート

template<class Fn> int call(Fn fn) {
  return fn(3);
}
auto fn = [](int v) { return v * 2; };
call(fn); // 6

関数呼び出しが行えるものであればどんな関数オブジェクトでも渡せます(関数、ラムダ、ファンクタ、ブロック等)。

関数ポインタ

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活用時よりもコンパイルスピードや実行時パフォーマンスが向上する場合があります。

コンパイルスピードや実行スピードの改善が必要になった際には両者の使い分けを検討してみると良いでしょう。

広告