今回は、C++でよく出てくる「std::functionとは何か」について解説していきます。
「std::functionって何?」
「関数ポインタと何が違うの?」
「ラムダやメンバ関数もまとめて扱えるって聞いたけど、どういうこと?」
こんな疑問はありませんか?
std::function は、ざっくり言うと「呼び出せるもの(関数・ラムダ・関数オブジェクト)を、型を気にせず1つの変数に入れておける入れ物」です。ボタンが押されたときの処理や、敵が死んだときのコールバックを「あとから差し替えられる」ようにするのに大活躍します。
この記事を読み終えると、あなたはstd::functionの役割・書き方・関数ポインタとの違い・ゲームでの使いどころ・ハマりやすいミスをしっかり理解できると思いますので、ぜひ最後まで読んでいただけると嬉しいです。
std::functionの正体
std::function は <functional> にある標準ライブラリの型です。「どんな引数を受け取って、何を返すか」だけを決めておけば、中身は自由に差し替えられるのが特徴です。
#include <functional>
#include <iostream>
// int を2つ受け取って int を返す「入れ物」
std::function<int(int, int)> op;
int Add(int a, int b) { return a + b; }
int main() {
op = Add; // 普通の関数を入れる
std::cout << op(2, 3) << "n"; // 5
op = [](int a, int b) { // ラムダを入れる
return a * b;
};
std::cout << op(2, 3) << "n"; // 6
return 0;
}std::function<int(int, int)> の int(int, int) の部分が「型(シグネチャ)」です。同じ入れ物に、普通の関数もラムダも入れられるのが分かりますね。
関数ポインタとの違い
「それ、関数ポインタでもできるのでは?」と思うかもしれません。大きな違いは「状態を持ったラムダ(キャプチャ付き)」も入れられることです。
- 関数ポインタ … 素の関数しか入らない。キャプチャありラムダは不可
- std::function … 関数・キャプチャありラムダ・関数オブジェクトを何でも入れられる
そのぶん、std::function は内部で少しだけ余分な処理をしています。柔軟さと引き換えに、生の関数ポインタよりわずかに遅い、という関係です。
ゲームでの使いどころ|コールバックを差し替える
一番おいしい使い方がコールバックです。UIボタンに「押されたら何をするか」を外から渡す、という形にできます。
class Button {
public:
// 「押されたときの処理」を外から受け取る
void SetOnClick(std::function<void()> callback) {
onClick_ = callback;
}
void Press() {
if (onClick_) onClick_(); // 入っていれば呼ぶ
}
private:
std::function<void()> onClick_;
};
// 使う側
Button start;
int score = 0;
start.SetOnClick([&]() {
score += 100; // キャプチャした score を触れる
});
start.Press(); // score が 100 になるButton クラスは「何をするか」を一切知りません。処理を丸ごと外から差し込めるので、ボタンごとに違う動きを付けられます。似た考え方のオブザーバーパターンと組み合わせると、さらに疎結合な設計になります。
【重要】私がstd::functionで固まった体験談
コールバックにラムダで [&](参照キャプチャ)を使い、その場の一時オブジェクトを参照していたら、呼び出した瞬間にクラッシュしました。
原因はダングリング参照。ラムダを登録したあとに、参照していた変数が寿命を迎えて消えていたんです。std::function は中身を保持し続けるので、「いつ呼ばれるか分からないコールバックで [&] を使うと危ない」と痛感しました。長生きするコールバックでは、値キャプチャ [=] か、必要な値だけをコピーで渡すのが安全です。
ここを理解してからは、コールバックの寿命を意識するようになり、原因不明のクラッシュがぐっと減りました。
std::functionのよくある失敗例と対処法
①空のまま呼び出してクラッシュ
何も入れていない std::function を呼ぶと std::bad_function_call で落ちます。呼ぶ前に if (f) で中身があるか確認しましょう。
②[&]キャプチャでダングリング参照
体験談のとおりです。すぐ呼ぶなら [&] でOKですが、あとで呼ばれるコールバックは値キャプチャを基本にすると安全です。
③毎フレーム大量に作って重い
std::function は生成・コピーに多少コストがあります。更新ループの中で毎回作り直さず、一度作って使い回すのが定石です。ホットな場所では関数ポインタやテンプレートも検討します。
注意点
- 呼ぶ前に
if (f)で空チェックする - 長生きするコールバックで
[&]を使わない(寿命に注意) - 速度が命の場所では関数ポインタやテンプレートも選択肢に
まとめ
- std::functionは呼び出せるものを入れておける入れ物
- 関数ポインタと違いキャプチャありラムダも入る
- ゲームではコールバックの差し替えで活躍する
- ハマりどころは空呼び出しと参照キャプチャの寿命
std::function を使えると、処理を「データのように渡す」設計ができるようになります。まずはボタンのコールバックから試してみてください。


