Menu
Coddy logo textTech

Expresiones Lambda en profundidad

Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 85 de 104.

Las expresiones lambda, introducidas en C++11, son funciones anónimas que puedes definir en línea. Si bien has visto lambdas básicas con algoritmos de la STL, comprender su sintaxis completa desbloquea capacidades potentes para capturar variables y controlar cómo se accede a ellas.

La sintaxis completa de una lambda es: [capture](parameters) mutable -> return_type { body }. La cláusula de captura determina a qué variables externas puede acceder la lambda y cómo:

#include <iostream>

int main() {
    int x = 10;
    int y = 20;
    
    auto byValue = [x]() { return x * 2; };        // Copia de x
    auto byRef = [&y]() { y += 5; };               // Referencia a y
    auto allByValue = [=]() { return x + y; };     // Copia todo
    auto allByRef = [&]() { x++; y++; };           // Referencia todo
    auto mixed = [x, &y]() { y += x; };            // Mezcla ambos
    
    byRef();
    std::cout << y << "\n";  // 25
}

Por defecto, las variables capturadas por valor son const dentro de la lambda. La palabra clave mutable permite la modificación de estas copias:

int counter = 0;
auto increment = [counter]() mutable {
    return ++counter;  // Modifica la copia de la lambda
};

std::cout << increment() << "\n";  // 1
std::cout << increment() << "\n";  // 2
std::cout << counter << "\n";      // 0 - el original no cambia

C++14 añadió capturas de inicialización, permitiéndote crear nuevas variables o mover objetos a la lambda:

auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
    return *p;
};  // Propiedad transferida a la lambda

Las lambdas son particularmente útiles en la POO cuando necesitas pasar comportamiento como un parámetro (para callbacks, comparadores personalizados o manejadores de eventos) sin definir objetos de función separados.

challenge icon

Desafío

Fácil

Vamos a construir un sistema de manejo de eventos que muestre el poder de las expresiones lambda con diferentes modos de captura. Crearás un despachador de eventos simple que almacena e invoca callbacks, demostrando cómo las lambdas pueden capturar el estado externo de varias maneras.

Organizarás tu código en tres archivos:

  • EventDispatcher.h: Define una clase EventDispatcher que gestiona los callbacks de eventos.

    Tu despachador debe almacenar los callbacks usando un std::vector de std::function<void()>. Incluye estos métodos:

    • addCallback(std::function<void()> callback) — añade un callback a la lista
    • fireAll() — invoca todos los callbacks almacenados en orden
    • clear() — elimina todos los callbacks

    Necesitarás incluir <functional> y <vector>.

  • EventDispatcher.cpp: Implementa los métodos para tu despachador. El método fireAll() simplemente debe iterar a través de todos los callbacks e invocar cada uno.
  • main.cpp: Lee dos entradas:
    1. Un número base (entero)
    2. Un multiplicador (entero)

    Crea un EventDispatcher y demuestra diferentes técnicas de captura de lambda añadiendo tres callbacks:

    1. Una lambda que capture el número base por valor e imprima: Base value: [base]
    2. Una lambda que capture el multiplicador por referencia, lo incremente en 1, y luego imprima: Multiplier after increment: [multiplier]
    3. Una lambda mutable que capture una variable contador (inicializada en 0) por valor, la incremente cada vez que sea llamada, e imprima: Call count: [counter]

    Después de añadir todos los callbacks, llama a fireAll() dos veces para ver cómo se comportan los diferentes modos de captura a través de múltiples invocaciones. Entre las dos llamadas a fireAll(), imprime --- como separador.

    Finalmente, después de ambas rondas, imprime el valor final de la variable multiplicador desde main para mostrar cómo le afectó la captura por referencia: Final multiplier: [multiplier]

Por ejemplo, con las entradas 10 y 5:

Base value: 10
Multiplier after increment: 6
Call count: 1
---
Base value: 10
Multiplier after increment: 7
Call count: 1
Final multiplier: 7

Con las entradas 42 y 0:

Base value: 42
Multiplier after increment: 1
Call count: 1
---
Base value: 42
Multiplier after increment: 2
Call count: 1
Final multiplier: 2

Observa los comportamientos clave: la captura por valor mantiene la base original sin cambios, la captura por referencia modifica la variable multiplicador real en main (acumulándose entre llamadas), y el contador de la lambda mutable se comporta según cómo se almacenen y llamen los objetos lambda; observa cómo persiste el estado interno en tu implementación al usar std::function.

Hoja de referencia

Las expresiones lambda son funciones anónimas que pueden definirse en línea. La sintaxis completa es:

[capture](parameters) mutable -> return_type { body }

Cláusulas de captura

La cláusula de captura determina a qué variables externas puede acceder la lambda y cómo:

int x = 10;
int y = 20;

auto byValue = [x]() { return x * 2; };        // Copy of x
auto byRef = [&y]() { y += 5; };               // Reference to y
auto allByValue = [=]() { return x + y; };     // Copy all variables
auto allByRef = [&]() { x++; y++; };           // Reference all variables
auto mixed = [x, &y]() { y += x; };            // Mix both modes

Lambdas mutables

Por defecto, las variables capturadas por valor son const dentro de la lambda. Use mutable para modificar las copias:

int counter = 0;
auto increment = [counter]() mutable {
    return ++counter;  // Modifies the lambda's copy
};

std::cout << increment() << "\n";  // 1
std::cout << increment() << "\n";  // 2
std::cout << counter << "\n";      // 0 - original unchanged

Capturas de inicialización (C++14)

Cree nuevas variables o mueva objetos dentro de la lambda:

auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
    return *p;
};  // Ownership transferred into lambda

Uso de lambdas con std::function

Almacene lambdas en std::function para callbacks y manejadores de eventos:

#include <functional>
#include <vector>

std::vector<std::function<void()>> callbacks;
callbacks.push_back([x]() { /* use x */ });
callbacks.push_back([&y]() { /* modify y */ });

Pruébalo tú mismo

#include <iostream>
#include "EventDispatcher.h"

using namespace std;

int main() {
    int base;
    int multiplier;
    cin >> base;
    cin >> multiplier;
    
    EventDispatcher dispatcher;
    
    // TODO: Añadir una lambda que capture base POR VALOR
    // Debe imprimir: "Base value: [base]"
    
    // TODO: Añadir una lambda que capture multiplier POR REFERENCIA
    // Debe incrementar multiplier en 1, luego imprimir: "Multiplier after increment: [multiplier]"
    
    // TODO: Añadir una lambda MUTABLE que capture un contador (inicializado en 0) por valor
    // Debe incrementar el contador e imprimir: "Call count: [counter]"
    
    // TODO: Llamar a fireAll() para invocar todos los callbacks
    
    // TODO: Imprimir "---" como separador
    
    // TODO: Llamar a fireAll() de nuevo
    
    // TODO: Imprimir el valor final de multiplier: "Final multiplier: [multiplier]"
    
    return 0;
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos