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++; }; // Referencia 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 OOP 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 demonstre 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: "seguido do valor de base - Uma lambda que captura o multiplicador por referência, incrementa-o em 1 e, em seguida, imprime:
"Multiplier after increment: "seguido do valor do multiplicador - Uma lambda mutable que captura uma variável de contador (inicializada em 0) por valor, incrementa-a cada vez que é chamada e imprime:
"Call count: "seguido do valor do contador
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: "seguido do valor do multiplicador
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: 2
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: 2
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 mutável persiste seu estado interno entre as chamadas de fireAll() porque o mesmo objeto lambda armazenado está sendo invocado.
Folha de consulta
Expressões lambda são funções anônimas que podem ser definidas inline. A sintaxe completa é:
[capture](parameters) mutable -> return_type { body }Cláusulas de Captura
A cláusula de captura determina quais variáveis externas a lambda pode acessar e como:
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; }; // Copiar todas as variáveis
auto allByRef = [&]() { x++; y++; }; // Referenciar todas as variáveis
auto mixed = [x, &y]() { y += x; }; // Misturar ambos os modosLambdas Mutáveis
Por padrão, variáveis capturadas por valor são const dentro da lambda. Use mutable para modificar as 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 inalteradoCapturas de Inicialização (C++14)
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 lambdaUsando Lambdas com std::function
Armazene lambdas em std::function para callbacks e manipuladores de eventos:
#include <functional>
#include <vector>
std::vector<std::function<void()>> callbacks;
callbacks.push_back([x]() { /* usar x */ });
callbacks.push_back([&y]() { /* modificar y */ });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
// Ela deve imprimir: "Base value: [base]"
// TODO: Adicione uma lambda que capture multiplier POR REFERÊNCIA
// Ela deve incrementar multiplier em 1 e, em seguida, imprimir: "Multiplier after increment: [multiplier]"
// TODO: Adicione uma lambda MUTABLE que capture um contador (inicializado em 0) por valor
// Ela 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