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++; };           // 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 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 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.

challenge icon

Desafio

Fácil

Vamos 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 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: " seguido do valor de base
    2. 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
    3. 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 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: " 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: 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: 2
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 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 modos

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

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

Usando 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;
}
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