Última atualização:2026-10-12
C++ std::bind versus lambda¶
Em entrevistas é pergunta comum: tanto std::bind quanto lambda conseguem empacotar uma função e seus argumentos num objeto chamável — qual usar? Todos decoram a resposta, «use lambda», mas por quê? Além da legibilidade, quão grande é a diferença de desempenho afinal? Sem enrolação: escrevi um benchmark e medi.
(1) Objeto de teste: a mesma função-membro Calculator::calculate, empacotada respectivamente com std::bind e com uma lambda, passada para a mesma função template f e chamada repetidamente;
(2) por justiça, f recebe o objeto chamável como parâmetro de template (para que o overhead de apagamento de tipos do std::function não contamine a conclusão);
(3) código e resultados: 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);
Como ler os resultados¶
A versão lambda sai claramente mais rápida que a versão bind, por dois motivos principais:
(1) O tipo binder devolvido por std::bind guarda como membros «o chamável amarrado + cada argumento placeholder»; uma chamada precisa atravessar uma longa cadeia de encaminhamentos internos da biblioteca padrão, e nas implementações antigas os objetos bind tinham semântica de cópia pesada e eram difíceis de inline. Uma lambda é só um objeto de closure: guarda exatamente o que captura, e o compilador faz inline sem nenhum obstáculo.
(2) Um ponto mais sutil: std::bind difere da lambda no tratamento da ordem de avaliação dos argumentos, da resolução de sobrecargas e dos argumentos padrão, e na época do C++11 as implementações ainda divergiam (depois o padrão apertou o comportamento). O Effective Modern C++, Item 34, tem o resumo completo: se puder usar lambda, não use bind.
O cenário que realmente sobra para o bind é basicamente um: antes do C++14 não havia lambdas genéricas nem init-capture, e os placeholders do std::bind davam conta de «reordenar argumentos / pré-amarrar parte deles». Hoje a lambda faz tudo isso, e de modo mais intuitivo.
Uma questão: mudando o f acima de template para receber um std::function<void(int)>, como mudaria a distância entre os dois benchmarks e por quê?
Tópicos relacionados¶
- Princípio e uso de std::move em C++ | C++那些事
- Visão geral dos novos recursos do C++11 | C++那些事
- Templates variádicos em C++ | C++那些事