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. Aunque has visto lambdas básicas con algoritmos de STL, comprender su sintaxis completa permite aprovechar funcionalidades avanzadas 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 todos
auto allByRef = [&]() { x++; y++; }; // Referencia todos
auto mixed = [x, &y]() { y += x; }; // Mezcla ambos
byRef();
std::cout << y << "\n"; // 25
}De forma predeterminada, las variables capturadas por valor son const dentro de la lambda. La palabra clave mutable permite modificar 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 - original sin cambiosC++14 añadió las capturas de inicialización, lo que te permite crear nuevas variables o mover objetos dentro de la lambda:
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
return *p;
}; // Propiedad transferida a la lambdaLas lambdas son especialmente útiles en la POO cuando necesitas pasar un comportamiento como parámetro, por ejemplo, para callbacks, comparadores personalizados o manejadores de eventos, sin definir objetos de función independientes.
Desafío
FácilConstruyamos 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 sencillo que almacena e invoca callbacks, demostrando cómo las lambdas pueden capturar estado externo de varias maneras.
Organizarás tu código en tres archivos:
EventDispatcher.h: Define una claseEventDispatcherque gestiona los callbacks de eventos.Tu despachador debe almacenar los callbacks usando un
std::vectordestd::function<void()>. Incluye estos métodos:addCallback(std::function<void()> callback): añade un callback a la listafireAll(): invoca todos los callbacks almacenados en ordenclear(): elimina todos los callbacks
Deberás incluir
<functional>y<vector>.EventDispatcher.cpp: Implementa los métodos de tu despachador. El métodofireAll()simplemente debe iterar por todos los callbacks e invocar cada uno.main.cpp: Lee dos entradas:- Un número base (entero)
- Un multiplicador (entero)
Crea un
EventDispatchery demuestra diferentes técnicas de captura de lambdas añadiendo tres callbacks:- Una lambda que captura el número base por valor e imprime:
Base value: [base] - Una lambda que captura el multiplicador por referencia, lo incrementa en 1 y después imprime:
Multiplier after increment: [multiplier] - Una lambda mutable que captura una variable contador (inicializada en 0) por valor, la incrementa cada vez que se llama y muestra:
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 en varias invocaciones. Entre las dos llamadas afireAll(), imprime---como separador.Finalmente, después de ambas rondas, imprime el valor final de la variable multiplicador de 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: 7Con 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: 2Observa los comportamientos clave: la captura por valor mantiene sin cambios el base original, la captura por referencia modifica la variable multiplicador real en main (acumulándose entre llamadas) y el contador de la lambda mutable muestra 1 en cada ronda. Esto se debe a que fireAll() itera por valor sobre sus callbacks: en cada ronda invoca una copia nueva del std::function almacenado, cuyo contador capturado comienza en 0 y se incrementa a 1, por lo que el estado propio del callback almacenado nunca avanza.
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ñade una lambda que capture base POR VALOR
// Debería imprimir: "Base value: [base]"
// TODO: Añade una lambda que capture multiplier POR REFERENCIA
// It should increment multiplier by 1, then print: "Multiplier after increment: [multiplier]"
// TODO: Añade una lambda MUTABLE que capture un contador (inicializado a 0) por valor
// Debería incrementar el contador e imprimir: "Call count: [counter]"
// TODO: Llama a fireAll() para invocar todos los callbacks
// TODO: Imprime "---" como separador
// TODO: Llama a fireAll() de nuevo
// TODO: Print the final multiplier value: "Final multiplier: [multiplier]"
return 0;
}
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
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico12Características de C++ moderno
Semántica de movimiento y RvaluesReenvío perfectoExpresiones Lambda en profundidadstd::function y std::bindconstexpr y constevalBindings estructuradosoptional, variant, anyPractica por tu cuenta: Compilador de C++ online