Последнее обновление: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'ами и почему?
Похожие темы¶
- Принцип работы и применение std::move в C++ | C++那些事
- Обзор новых возможностей C++11 | C++那些事
- Вариативные шаблоны в C++ | C++那些事