Menu
Coddy logo textTech

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 inalterado

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

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

challenge icon

Desafio

Fácil

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

    Seu despachante deve armazenar callbacks usando 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 na ordem
    • clear(): remove todos os callbacks

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

  • EventDispatcher.cpp: implemente os métodos do 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 capture o número base por valor e imprima: Base value: [base]
    2. Uma lambda que capture o multiplicador por referência, incremente-o em 1 e então imprima: Multiplier after increment: [multiplier]
    3. 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 de fireAll(), 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: 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 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;
}
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

Pratique por conta própria: Compilador de C++ online