Funtores y expresiones lambda
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 74 de 104.
Muchos algoritmos de STL aceptan un objeto invocable que personaliza su comportamiento. Ya has visto lambdas utilizadas con std::transform. Exploremos tanto los functors como las expresiones lambda: dos formas de crear objetos invocables en C++.
Un functor (objeto función) es una clase que sobrecarga el operator(), lo que permite llamar a sus instancias como funciones:
#include <algorithm>
#include <vector>
#include <iostream>
struct MultiplyBy {
int factor;
MultiplyBy(int f) : factor(f) {}
int operator()(int x) const {
return x * factor;
}
};
int main() {
std::vector<int> nums = {1, 2, 3, 4};
std::vector<int> result(nums.size());
std::transform(nums.begin(), nums.end(), result.begin(), MultiplyBy(3));
// result: {3, 6, 9, 12}
}Cada instancia de un functor puede mantener su propio estado independiente mediante variables miembro establecidas durante la construcción, algo que una llamada a una función ordinaria no puede hacer. Sin embargo, definir una clase para operaciones sencillas resulta verboso. Las expresiones lambda ofrecen una alternativa concisa:
int factor = 3;
std::transform(nums.begin(), nums.end(), result.begin(),
[factor](int x) { return x * factor; });La sintaxis de lambda es [capture](parameters) { body }. La cláusula de captura especifica a qué variables externas puede acceder la lambda. Usa [=] para capturarlas todas por valor, [&] para capturarlas todas por referencia, o enumera variables específicas como [factor] o [&factor].
Las lambdas son especialmente útiles para operaciones puntuales con algoritmos como la ordenación con criterios personalizados:
std::vector<int> nums = {5, -2, 8, -1};
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return std::abs(a) < std::abs(b); });
// Ordenado por valor absoluto: {-1, -2, 5, 8}Desafío
FácilConstruyamos una calculadora de precios que demuestre tanto los funtores como las expresiones lambda para aplicar distintas estrategias de descuento a los precios de los productos.
Organizarás tu código en dos archivos:
Discounts.h: Define aquí tus funtores de descuento y funciones auxiliares.Crea un functor llamado
PercentageDiscountque almacene un porcentaje de descuento (como un entero). Suoperator()debe recibir un preciodoubley devolver el precio con descuento. Por ejemplo, un descuento del 20 % sobre $100 debe devolver $80.Crea otro functor llamado
FixedDiscountque almacene una cantidad fija para restar (como undouble). Suoperator()debe recibir un precio y devolver el precio menos la cantidad fija (pero nunca un valor inferior a 0).Crea una función llamada
printPricesque reciba unconst std::vector<double>&e imprima todos los precios separados por espacios, seguidos de un salto de línea. Da formato a cada precio con dos decimales.main.cpp: Lee cinco entradas (cada una en una línea separada):- Precio del primer producto (double)
- Precio del segundo producto (double)
- Precio del tercer producto (double)
- Descuento porcentual que se aplicará (entero, por ejemplo, 20 para 20 %)
- Cantidad del descuento fijo (double)
Crea un vector con los tres precios y demuestra ambos enfoques:
- Imprime
Original prices:seguido de los precios - Usa
std::transformcon tu functorPercentageDiscountpara crear un nuevo vector de precios con descuento. ImprimeAfter percentage discount:seguido de los resultados - Usa
std::transformcon tu functorFixedDiscountsobre los precios originales para crear otro vector. ImprimeAfter fixed discount:seguido de los resultados - Usa
std::transformcon una expresión lambda que duplique cada precio original. ImprimePremium prices (doubled):seguido de los resultados - Usa
std::sortcon una lambda para ordenar los precios originales en orden descendente. ImprimeSorted (high to low):seguido de los precios ordenados
Por ejemplo, con las entradas 100.00, 50.00, 75.00, 20 y 15.00:
Original prices: 100.00 50.00 75.00
After percentage discount: 80.00 40.00 60.00
After fixed discount: 85.00 35.00 60.00
Premium prices (doubled): 200.00 100.00 150.00
Sorted (high to low): 100.00 75.00 50.00 Este desafío te permite comparar los funtores (que mantienen un estado, como la cantidad del descuento) con las lambdas (que capturan variables para realizar operaciones rápidas en línea). Ambos enfoques funcionan perfectamente con algoritmos de STL como std::transform y std::sort.
Pruébalo tú mismo
#include <iostream>
#include <vector>
#include <algorithm>
#include "Discounts.h"
int main() {
// Leer entradas
double price1, price2, price3;
int percentageDiscount;
double fixedDiscount;
std::cin >> price1;
std::cin >> price2;
std::cin >> price3;
std::cin >> percentageDiscount;
std::cin >> fixedDiscount;
// Crear un vector con los tres precios
std::vector<double> prices = {price1, price2, price3};
// TODO: Print "Original prices:" followed by the prices using printPrices
// TODO: Use std::transform with PercentageDiscount functor
// Crear un nuevo vector para los resultados
// Print "After percentage discount:" followed by the results
// TODO: Use std::transform with FixedDiscount functor on original prices
// Crear un nuevo vector para los resultados
// Print "After fixed discount:" followed by the results
// TODO: Use std::transform with a lambda that doubles each original price
// Crear un nuevo vector para los resultados
// Print "Premium prices (doubled):" followed by the results
// TODO: Use std::sort with a lambda to sort original prices in descending order
// Print "Sorted (high to low):" followed by the sorted prices
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 empleados10Visión general de la STL
Visión general y filosofía de la STLContenedores de la STLIteradoresAlgoritmos de la STLFuntores y expresiones lambdaResumen - Frecuencia de palabras2Gestió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éricoPractica por tu cuenta: Compilador de C++ online