Перейти к содержанию

Последнее обновление:2026-10-12

Сравнение std::bind и lambda в C++

На собеседованиях часто спрашивают: и std::bind, и лямбда умеют упаковать функцию с аргументами в вызываемый объект — что же выбрать? Ответ все заучили: «используйте лямбду», но почему? Помимо читаемости, насколько велика разница в производительности на самом деле? Говорить легко — я написал benchmark и замерил.

(1) Подопытный: одна и та же функция-член Calculator::calculate, упакованная отдельно через std::bind и через лямбду, передаётся в одну и ту же шаблонную функцию 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);

Как читать результаты

Лямбда-версия заметно быстрее bind-версии, по двум основным причинам:

(1) Тип binder, возвращаемый std::bind, хранит «связанный вызываемый объект + все плейсхолдеры-аргументы» как члены; вызов проходит через длинную цепочку внутренних пересылок стандартной библиотеки, а в ранних реализациях семантика копирования у bind-объектов была тяжёлой и инлайнинг затруднён. Лямбда же — просто объект-замыкание: хранит ровно то, что захватила, и компилятор инлайнит её без препятствий.

(2) Более коварный момент: std::bind отличается от лямбды в обработке порядка вычисления аргументов, разрешения перегрузок и аргументов по умолчанию, а в эпоху C++11 реализации ещё расходились (позже стандарт ужесточил поведение). Полное резюме есть в Effective Modern C++, Item 34: если можно использовать лямбду — не используйте bind.

Единственный по-настоящему оставшийся сценарий для bind: до C++14 не было generic lambda и init-capture, а плейсхолдеры std::bind умели «переупорядочивать аргументы / заранее связывать их часть». Сейчас всё это делает лямбда, и нагляднее.

Вопрос на подумать: если изменить f выше с шаблона на приём std::function<void(int)>, как изменится разрыв между двумя benchmark'ами и почему?

Похожие темы

Комментарии