Menu
Coddy logo textTech

Reenvío perfecto

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

Al escribir funciones de plantilla que aceptan argumentos y los pasan a otras funciones, surge un problema: ¿cómo se preserva si el argumento original era un lvalue o un rvalue? El reenvío perfecto (perfect forwarding) resuelve esto pasando los argumentos exactamente como se recibieron, manteniendo su categoría de valor.

Los ingredientes clave son las referencias de reenvío (forwarding references) (escritas como T&& en un contexto de plantilla) y std::forward. Una referencia de reenvío puede vincularse tanto a lvalues como a rvalues, y std::forward realiza una conversión (cast) condicional del argumento a su tipo original:

#include <iostream>
#include <utility>

void process(int& x) { std::cout << "lvalue: " << x << "\n"; }
void process(int&& x) { std::cout << "rvalue: " << x << "\n"; }

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

int main() {
    int n = 10;
    wrapper(n);       // Llama a process(int&) - lvalue preservado
    wrapper(20);      // Llama a process(int&&) - rvalue preservado
}

Sin std::forward, los parámetros con nombre son siempre lvalues dentro de la función, incluso si se pasó un rvalue. Esto evitaría que la semántica de movimiento funcione correctamente al realizar el reenvío a constructores u otras funciones.

El reenvío perfecto (perfect forwarding) es esencial para escribir funciones de fábrica (factory functions), clases envolventes (wrapper classes) y cualquier código genérico que necesite construir objetos o invocar funciones preservando la intención del llamador con respecto a los movimientos frente a las copias.

challenge icon

Desafío

Fácil

Vamos a construir una función de fábrica que demuestre el reenvío perfecto (perfect forwarding) construyendo objetos mientras se preserva la categoría de valor de los argumentos que se le pasan. Crearás una función genérica make_object que reenvía argumentos al constructor de una clase, asegurando que los lvalues se copien y los rvalues se muevan.

Organizarás tu código en tres archivos:

  • Widget.h: Define una clase Widget que rastrea cómo recibe sus datos.

    Tu Widget debe almacenar un nombre std::string y un valor int. Proporciona dos constructores:

    • Uno que tome const std::string& (referencia lvalue) e int — imprime Widget constructed (copy): [name]
    • Uno que tome std::string&& (referencia rvalue) e int — imprime Widget constructed (move): [name] y mueve la cadena

    También añade un método display() que imprima [name]: [value].

  • Factory.h: Crea tu función de fábrica de reenvío perfecto.

    Escribe una función de plantilla make_object que acepte una referencia de reenvío (forwarding reference) para el nombre y un int regular para el valor. Usa std::forward para pasar el argumento del nombre al constructor de Widget preservando su categoría de valor original.

    La función debe devolver el Widget construido por valor.

  • main.cpp: Lee dos entradas:
    1. Un nombre (string)
    2. Un valor (entero)

    Demuestra el reenvío perfecto creando dos widgets:

    1. Primero, almacena el nombre en una variable local y pásalo a make_object como un lvalue
    2. Segundo, pasa una cadena temporal (el mismo nombre concatenado con "_temp") directamente a make_object como un rvalue

    Después de crear cada widget, llama a su método display().

Por ejemplo, con las entradas Gadget y 42:

Widget constructed (copy): Gadget
Gadget: 42
Widget constructed (move): Gadget_temp
Gadget_temp: 42

Con las entradas Device y 100:

Widget constructed (copy): Device
Device: 100
Widget constructed (move): Device_temp
Device_temp: 100

La idea clave es que tu función make_object no sabe si recibió un lvalue o un rvalue, pero al usar std::forward, preserva esa información al llamar al constructor de Widget. Esto permite que la resolución de sobrecarga del constructor seleccione la versión adecuada, permitiendo movimientos eficientes para temporales mientras se copian de forma segura las variables con nombre.

Hoja de referencia

El reenvío perfecto (perfect forwarding) permite que las funciones de plantilla pasen argumentos a otras funciones preservando su categoría de valor original (lvalue o rvalue).

El mecanismo utiliza referencias de reenvío (T&& en el contexto de la plantilla) combinadas con std::forward:

#include <utility>

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

Una referencia de reenvío puede vincularse tanto a lvalues como a rvalues. std::forward<T> realiza un cast condicional del argumento de vuelta a su tipo original.

Sin std::forward, los parámetros con nombre dentro de una función siempre se tratan como lvalues, incluso si se pasó un rvalue. Esto evitaría que la semántica de movimiento funcione correctamente.

Ejemplo que demuestra la preservación de la categoría de valor:

void process(int& x) { std::cout << "lvalue: " << x << "\n"; }
void process(int&& x) { std::cout << "rvalue: " << x << "\n"; }

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

int n = 10;
wrapper(n);       // Llama a process(int&) - lvalue preservado
wrapper(20);      // Llama a process(int&&) - rvalue preservado

El reenvío perfecto es esencial para funciones de fábrica, clases envolventes y código genérico que construye objetos o invoca funciones preservando la intención del llamador con respecto a movimientos frente a copias.

Pruébalo tú mismo

#include <iostream>
#include <string>
#include "Widget.h"
#include "Factory.h"

using namespace std;

int main() {
    // Leer entrada
    string name;
    int value;
    cin >> name >> value;
    
    // TODO: Crear el primer widget pasando name como un lvalue
    // Almacenar el name en una variable local y pasarla a make_object
    
    
    // TODO: Llamar a display() en el primer widget
    
    
    // TODO: Crear el segundo widget pasando una cadena temporal como un rvalue
    // Pasar (name + "_temp") directamente a make_object
    
    
    // TODO: Llamar a display() en el segundo widget
    
    
    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