Expressões Lambda em Profundidade
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy. Lição 85 de 104.
As expressões lambda, introduzidas no C++11, são funções anônimas que você pode definir diretamente no código. Embora você já tenha visto lambdas básicas com algoritmos da STL, compreender sua sintaxe completa permite aproveitar recursos poderosos para capturar variáveis e controlar como elas são acessadas.
A sintaxe completa de 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; }; // Cópia de todos
auto allByRef = [&]() { x++; y++; }; // Referência a todos
auto mixed = [x, &y]() { y += x; }; // Mistura ambos
byRef();
std::cout << y << "\n"; // 25
}Por padrão, as variáveis capturadas por valor são const dentro da lambda. A palavra-chave mutable permite modificar essas 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 as capturas de inicialização, permitindo criar novas variáveis ou mover objetos para dentro da lambda:
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
return *p;
}; // Propriedade transferida para a lambdaAs lambdas são particularmente úteis em OOP quando você precisa passar um comportamento como parâmetro — para callbacks, comparadores personalizados ou manipuladores de eventos — sem definir objetos de função separados.
Desafio
FácilVamos criar um sistema de manipuladores 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 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
std::vectordestd::function<void()>. Inclua estes métodos:addCallback(std::function<void()> callback): adiciona um callback à listafireAll(): invoca todos os callbacks armazenados na ordemclear(): remove todos os callbacks
Você precisará incluir
<functional>e<vector>.EventDispatcher.cpp: implemente os métodos do 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 capture o número base por valor e imprima:
Base value: [base] - Uma lambda que capture o multiplicador por referência, incremente-o em 1 e então imprima:
Multiplier after increment: [multiplier] - Uma lambda mutable que capture uma variável contador (inicializada como 0) por valor, incremente-a cada vez que for chamada e imprima:
Call count: [counter]
Depois de adicionar todos os callbacks, chame
fireAll()duas vezes para ver como os diferentes modos de captura se comportam em várias invocações. Entre as duas chamadas defireAll(), imprima---como separador.Por fim, após as duas rodadas, imprima o valor final da variável multiplicador de 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 o valor base original inalterado, a captura por referência modifica a variável multiplicador real em main (acumulando entre as chamadas) e o contador da lambda mutable lê 1 em todas as rodadas. Isso acontece porque fireAll() itera por seus callbacks por valor: a cada rodada, uma cópia nova do std::function armazenado é invocada, cujo contador capturado começa em 0 e é incrementado para 1; portanto, o próprio estado do callback armazenado nunca avança.
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 captura base POR VALOR
// Deve imprimir: "Base value: [base]"
// TODO: Adicione uma lambda que captura multiplier POR REFERÊNCIA
// It should increment multiplier by 1, then print: "Multiplier after increment: [multiplier]"
// TODO: Adicione uma lambda MUTABLE que captura um counter (inicializado com 0) por valor
// Deve incrementar o counter e imprimir: "Call count: [counter]"
// TODO: Chame fireAll() para invocar todos os callbacks
// TODO: Imprima "---" como separador
// TODO: Chame fireAll() novamente
// TODO: Print the final multiplier value: "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 POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner Genérico12Recursos Modernos do C++
Semântica de Movimentação e RvaluesEncaminhamento PerfeitoExpressões Lambda em Profundidadestd::function e std::bindconstexpr e constevalVinculações Estruturadasoptional, variant, anyPratique por conta própria: Compilador de C++ online