Expressões Lambda em Detalhes
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 85 de 104.
Expressões lambda, introduzidas no C++11, são funções anônimas que você pode definir inline. Embora você tenha visto lambdas básicos com algoritmos STL, entender sua sintaxe completa desbloqueia recursos poderosos para capturar variáveis e controlar como elas são acessadas.
A sintaxe completa da lambda é: [capture](parameters) mutable -> return_type { body }. A cláusula de captura determina quais variáveis externas a lambda pode acessar e como:
#include <iostream>
int main() {
int x = 10;
int y = 20;
auto byValue = [x]() { return x * 2; }; // Cópia de x
auto byRef = [&y]() { y += 5; }; // Referência a y
auto allByValue = [=]() { return x + y; }; // Copia tudo
auto allByRef = [&]() { x++; y++; }; // Referência a tudo
auto mixed = [x, &y]() { y += x; }; // Mistura ambos
byRef();
std::cout << y << "\n"; // 25
}Por padrão, variáveis capturadas por valor são const dentro da lambda. A palavra-chave mutable permite a modificação dessas cópias:
int counter = 0;
auto increment = [counter]() mutable {
return ++counter; // Modifica a cópia da lambda
};
std::cout << increment() << "\n"; // 1
std::cout << increment() << "\n"; // 2
std::cout << counter << "\n"; // 0 - original inalteradoO C++14 adicionou capturas de inicialização (init captures), permitindo que você crie novas variáveis ou mova objetos para dentro da lambda:
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
return *p;
}; // Propriedade transferida para a lambdaLambdas são particularmente úteis em POO quando você precisa passar comportamento como um parâmetro - para callbacks, comparadores personalizados ou manipuladores de eventos - sem definir objetos de função separados.
Desafio
FácilVamos construir um sistema de tratamento de eventos que demonstra o poder das expressões lambda com diferentes modos de captura. Você criará um despachante de eventos simples que armazena e invoca callbacks, demonstrando como as lambdas podem capturar o estado externo de várias maneiras.
Você organizará seu código em três arquivos:
EventDispatcher.h: Defina uma classeEventDispatcherque gerencia callbacks de eventos.Seu despachante deve armazenar callbacks usando um
std::vectordestd::function<void()>. Inclua estes métodos:addCallback(std::function<void()> callback)— adiciona um callback à listafireAll()— invoca todos os callbacks armazenados em ordemclear()— remove todos os callbacks
Você precisará incluir
<functional>e<vector>.EventDispatcher.cpp: Implemente os métodos para o seu despachante. O métodofireAll()deve simplesmente iterar por todos os callbacks e invocar cada um.main.cpp: Leia duas entradas:- Um número base (inteiro)
- Um multiplicador (inteiro)
Crie um
EventDispatchere demonstre diferentes técnicas de captura de lambda adicionando três callbacks:- Uma lambda que captura o número base por valor e imprime:
Base value: [base] - Uma lambda que captura o multiplicador por referência, incrementa-o em 1 e, em seguida, imprime:
Multiplier after increment: [multiplier] - Uma lambda mutable que captura uma variável contadora (inicializada em 0) por valor, incrementa-a cada vez que é chamada e imprime:
Call count: [counter]
Após adicionar todos os callbacks, chame
fireAll()duas vezes para ver como os diferentes modos de captura se comportam em múltiplas invocações. Entre as duas chamadas defireAll(), imprima---como um separador.Finalmente, após ambas as rodadas, imprima o valor final da variável multiplicadora da função main para mostrar como a captura por referência a afetou:
Final multiplier: [multiplier]
Por exemplo, com as entradas 10 e 5:
Base value: 10
Multiplier after increment: 6
Call count: 1
---
Base value: 10
Multiplier after increment: 7
Call count: 1
Final multiplier: 7Com as entradas 42 e 0:
Base value: 42
Multiplier after increment: 1
Call count: 1
---
Base value: 42
Multiplier after increment: 2
Call count: 1
Final multiplier: 2Observe os comportamentos principais: a captura por valor mantém a base original inalterada, a captura por referência modifica a variável multiplicadora real na main (acumulando entre as chamadas), e o contador da lambda mutable lê 1 em cada rodada. Isso ocorre porque fireAll() itera seus callbacks por valor: cada rodada invoca uma cópia nova da std::function armazenada, cujo contador capturado começa em 0 e é incrementado para 1, portanto, o estado do próprio callback armazenado nunca é avançado.
Experimente você mesmo
#include <iostream>
#include "EventDispatcher.h"
using namespace std;
int main() {
int base;
int multiplier;
cin >> base;
cin >> multiplier;
EventDispatcher dispatcher;
// TODO: Adicione uma lambda que capture base POR VALOR
// Deve imprimir: "Base value: [base]"
// TODO: Adicione uma lambda que capture multiplier POR REFERÊNCIA
// Deve incrementar multiplier em 1, então imprimir: "Multiplier after increment: [multiplier]"
// TODO: Adicione uma lambda MUTÁVEL que capture um contador (inicializado em 0) por valor
// Deve incrementar o contador e imprimir: "Call count: [counter]"
// TODO: Chame fireAll() para invocar todos os callbacks
// TODO: Imprima "---" como separador
// TODO: Chame fireAll() novamente
// TODO: Imprima o valor final de multiplier: "Final multiplier: [multiplier]"
return 0;
}Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico12Recursos do C++ Moderno
Semântica de Movimento e RvaluesPerfect ForwardingExpressões Lambda em Detalhesstd::function & std::bindconstexpr e constevalStructured Bindingsoptional, variant, any