コンテンツにスキップ

最終更新:2026-10-12

C++ std::bind と lambda の比較

面接でよく聞かれます:std::bind と lambda はどちらも関数と引数を呼び出し可能オブジェクトひとつにまとめられますが、どちらを使うべきか? 答えはみんな「lambda を使う」と暗記しているけれど、なぜ? readability 以外に、性能差は実際どれほどあるのか? 口で言うだけでは上達しないので、benchmark を書いて実測しました。

(1)テスト対象:同じメンバ関数 Calculator::calculate を、それぞれ std::bind と lambda でパッケージし、同じテンプレート関数 f に渡して繰り返し呼び出す;

(2)公平を期すため、f はテンプレート引数で呼び出し可能オブジェクトを受け取ります(std::function の型消去オーバーヘッドが結論を歪めないように);

(3)コードと結果:https://quick-bench.com/q/CEnR_XUuIwnZxaP4KJNSlpYG38c

#include <functional>
#include <numeric>
#include <vector>



struct Calculator {
    int count{1};
    int total{0};

    auto calculate() -> void {
        using std::iota, std::vector;

        // Random math
        vector<int> u(8), v(Calculator::count * u.size());

        iota(v.begin(), v.end(), *u.begin());

        int sum{*v.begin() + *u.begin()};

        for (size_t pos{0}; pos < v.size(); ++pos) {
            sum += u[pos % u.size()] * v[pos];
        }

        Calculator::total += sum;
        ++Calculator::count;
    }
};

// auto f(std::function<void(int)> g) -> void { g(rand()); }

template<typename FN> auto f(FN g) -> void { g(rand()); }


static auto BM_bind(benchmark::State& state) -> void {
    Calculator calculator;
    for (auto _ : state) {
        f(std::bind(&Calculator::calculate, &calculator));
    }
}

static auto BM_lambda(benchmark::State& state) -> void {
    Calculator calculator;
    for (auto _ : state) {
        f([&calculator]([[maybe_unused]] int x) { calculator.calculate(); });
    }
}

BENCHMARK(BM_bind);
BENCHMARK(BM_lambda);

結果の読み方

実行すると lambda 版は bind 版より明らかに速く出ます。主な理由は 2 つ:

(1)std::bind が返す binder 型は「束縛した呼び出し可能オブジェクト+各プレースホルダ引数」をすべてメンバとして保持し、呼び出し時には標準ライブラリ内部の長い転送の連鎖を経由します。さらに初期の実装では bind オブジェクトのコピー意味論は重く、インライン化も困難でした。一方 lambda はただのクロージャオブジェクトで、キャプチャしたものだけを格納し、コンパイラのインライン化は何の障害もありません。

(2)より見えにくい点:std::bind は**引数の評価順序、オーバーロード解決、デフォルト引数**の扱いが lambda と異なり、C++11 の時代は実装ごとに不一致でした(後に標準が挙動を厳格化しました)。Effective Modern C++ の Item 34 に完全なまとめがあります:lambda が使えるなら bind は使うな。

bind に本当に残る場面はほぼ一つだけです:C++14 より前はジェネリック lambda も init-capture もなく、std::bind のプレースホルダが「引数の並べ替え/一部の事前束縛」を担えました。今はこれらはすべて lambda ででき、しかもより直感的です。

思考題を一つ:上の f をテンプレートから std::function<void(int)> を受け取る形に変えると、2 つの benchmark の差はどうなりますか? そしてなぜ?

関連トピック

コメント