Menu
Coddy logo textTech

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 inalterado

O 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 lambda

Lambdas 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.

challenge icon

Desafio

Fácil

Vamos 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 classe EventDispatcher que gerencia callbacks de eventos.

    Seu despachante deve armazenar callbacks usando um std::vector de std::function<void()>. Inclua estes métodos:

    • addCallback(std::function<void()> callback) — adiciona um callback à lista
    • fireAll() — invoca todos os callbacks armazenados em ordem
    • clear() — remove todos os callbacks

    Você precisará incluir <functional> e <vector>.

  • EventDispatcher.cpp: Implemente os métodos para o seu despachante. O método fireAll() deve simplesmente iterar por todos os callbacks e invocar cada um.
  • main.cpp: Leia duas entradas:
    1. Um número base (inteiro)
    2. Um multiplicador (inteiro)

    Crie um EventDispatcher e demonstre diferentes técnicas de captura de lambda adicionando três callbacks:

    1. Uma lambda que captura o número base por valor e imprime: Base value: [base]
    2. Uma lambda que captura o multiplicador por referência, incrementa-o em 1 e, em seguida, imprime: Multiplier after increment: [multiplier]
    3. 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 de fireAll(), 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: 7

Com 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: 2

Observe 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;
}
quiz iconTeste seus conhecimentos

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