Última actualización:2026-10-12
C++ std::bind frente a lambda¶
En las entrevistas se pregunta a menudo: tanto std::bind como lambda pueden empaquetar una función con sus argumentos en un objeto invocable, ¿cuál usar? Todo el mundo se sabe de memoria la respuesta, «usa lambda», pero ¿por qué? Más allá de la legibilidad, ¿qué tan grande es la diferencia de rendimiento? De la teoría a la práctica: escribí un benchmark y lo medí.
(1) Sujeto de prueba: la misma función miembro Calculator::calculate, empaquetada con std::bind y con una lambda respectivamente, pasada a la misma función plantilla f y llamada repetidamente;
(2) Para ser justos, f recibe el invocable como parámetro de plantilla (para que la sobrecarga de borrado de tipos de std::function no contamine la conclusión);
(3) Código y 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);
Cómo leer los resultados¶
La versión lambda sale claramente más rápida que la versión bind, por dos razones principales:
(1) El tipo binder que devuelve std::bind guarda como miembros «el invocable ligado + cada parámetro comodín»; una llamada tiene que atravesar una larga cadena de reenvíos internos de la biblioteca estándar, y en las implementaciones tempranas los objetos bind tenían una semántica de copia pesada y eran difíciles de alinear en línea. Una lambda es simplemente un objeto clausura: guarda exactamente lo que captura, y el compilador la alinea sin ningún obstáculo.
(2) Un punto más sutil: std::bind difiere de lambda en el tratamiento del orden de evaluación de los argumentos, la resolución de sobrecargas y los argumentos por defecto, y en la época de C++11 las implementaciones incluso discrepaban (luego el estándar ajustó el comportamiento). Effective Modern C++ Item 34 tiene el resumen completo: si puedes usar lambda, no uses bind.
El escenario que de verdad le queda a bind es básicamente uno: antes de C++14 no existían las lambdas genéricas ni la init-capture, y los comodines de std::bind permitían «reordenar argumentos / preligar parte de ellos». Hoy lambda hace todo eso, y de forma más intuitiva.
Una pregunta: cambia el f de arriba de plantilla a recibir un std::function<void(int)>; ¿cómo cambiaría la distancia entre los dos benchmarks y por qué?
Temas relacionados¶
- C++ std::move: principio y uso | C++那些事
- Panorama de las novedades de C++11 | C++那些事
- Plantillas variádicas en C++ | C++那些事