Expressões lambda em C++: quando elas realmente aceleram o código?
\n\nOlá, colega! Se você já deu uma olhada no Stack Overflow, provavelmente viu a pergunta que reuniu milhares de visualizações: "What is a lambda expression, and when should I use one?". E isso não é por acaso. Lambdas em C++ são um tópico que ao mesmo tempo simplifica a vida e gera muitos debates sobre otimização e desempenho.
\n\nHoje não vamos apenas dar uma definição seca. Vamos entender como as expressões lambda funcionam internamente, quando seu uso torna o código mais rápido e quando, ao contrário, cria problemas ocultos. Vamos lá!
\n\nO que é uma expressão lambda? Um olhar por dentro
\n\nFormalmente, uma expressão lambda é uma função anônima que pode ser definida diretamente no local da chamada. Mas, indo mais fundo, o compilador transforma cada lambda em um objeto funcional (functor) único. Aqui está um exemplo:
\n\n// Lambda\nauto add = [](int a, int b) { return a + b; };\n\n// No que isso se transforma (simplificado)\nclass __lambda_1 {\npublic:\n auto operator()(int a, int b) const { return a + b; }\n};\n__lambda_1 add;\n\nPonto-chave: cada lambda tem seu próprio tipo único. Isso significa que o compilador pode fazer inlining agressivo das chamadas — ao contrário de ponteiros para funções ou std::function.
Desempenho: onde as lambdas ganham?
\n\nVamos direto ao ponto. Aqui estão três cenários onde as lambdas proporcionam um ganho significativo de desempenho:
\n\n1. Inlining e ausência de chamadas indiretas
\n\nQuando você passa uma lambda para uma função template (por exemplo, std::sort), o compilador conhece o tipo exato. Isso permite que ele embuta o código da lambda diretamente na função chamadora. Compare:
// Opção 1: ponteiro para função (mais lento)\nbool compare(int a, int b) { return a < b; }\nstd::sort(vec.begin(), vec.end(), compare);\n\n// Opção 2: lambda (mais rápido)\nstd::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; });\n\nNo primeiro caso — chamada indireta através de ponteiro. No segundo — inlining direto. Em grandes volumes de dados (milhões de elementos), a diferença pode chegar a 20-30% no tempo.
\n\n2. Captura de variáveis sem cópias desnecessárias
\n\nLambdas permitem gerenciar a captura de forma flexível. A escolha correta entre [=], [&] e variáveis específicas afeta diretamente o desempenho:
std::vector<std::string> data;\nstd::string prefix = "prefix_";\n\n// Ruim: copiamos uma string pesada\nstd::for_each(data.begin(), data.end(), \n [=](std::string& s) { s = prefix + s; });\n\n// Bom: captura por referência\nstd::for_each(data.begin(), data.end(), \n [&](std::string& s) { s = prefix + s; });\n\nCaptura por valor ([=]) cria uma cópia de prefix dentro do functor. Para std::string, isso pode ser caro. Captura por referência ([&]) é leve.
3. Lambdas em algoritmos da STL
\n\nAlgoritmos modernos da STL (C++17/20) usam lambdas ativamente para computação paralela. Por exemplo, std::transform com a política de execução std::par:
std::vector<int> input(1000000);\nstd::vector<int> output(input.size());\n\n// Processamento paralelo\nstd::transform(std::execution::par, \n input.begin(), input.end(), output.begin(),\n [](int x) { return x * x + 2 * x + 1; });\n\nA lambda aqui é um candidato ideal para paralelização, pois não tem efeitos colaterais (função pura). O compilador e a biblioteca podem executá-la com segurança em várias threads.
\n\nQuando as lambdas podem prejudicar o desempenho?
\n\nSeria injusto falar apenas dos pontos positivos. Existem situações onde lambdas não são a melhor escolha:
\n\n1. Captura pesada por valor
\n\nSe você capt