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 se los pasan a otras funciones, surge un problema: ¿cómo se conserva si el argumento original era un lvalue o un rvalue? El forwarding perfecto resuelve esto pasando los argumentos exactamente como se recibieron y manteniendo su categoría de valor.

Los ingredientes clave son las referencias de reenvío (escritas como T&& en el contexto de una plantilla) y std::forward. Una referencia de reenvío puede vincularse tanto a lvalues como a rvalues, y std::forward convierte condicionalmente el argumento de nuevo 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 siempre son lvalues dentro de la función, incluso si se pasó un rvalue. Esto impediría que la semántica de movimiento funcionara correctamente al reenviarlos a constructores u otras funciones.

El reenvío perfecto es esencial para escribir funciones de fábrica, clases envoltorio y cualquier código genérico que necesite construir objetos o invocar funciones, preservando al mismo tiempo la intención del autor de la llamada con respecto a los movimientos frente a las copias.

challenge icon

Desafío

Fácil

Construyamos una función fábrica que demuestre el reenvío perfecto mediante la construcción de objetos mientras conserva la categoría de valor de los argumentos que se le pasan. Crearás una función genérica make_object que reenvía los argumentos al constructor de una clase, asegurándote de 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 registre cómo recibe sus datos.

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

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

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

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

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

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

  • main.cpp: Lee dos entradas:
    1. Un nombre (cadena)
    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. Después, pasa directamente a "_temp" una cadena temporal (el mismo nombre concatenado con 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, conserva esa información al llamar al constructor de Widget. Esto permite que la resolución de sobrecarga del constructor seleccione la versión adecuada, habilitando movimientos eficientes para los temporales y copiando de forma segura las variables con nombre.

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: Crea el primer widget pasando name como un lvalue
    // Almacena el name en una variable local y pásalo a make_object
    
    
    // TODO: Llama a display() en el primer widget
    
    
    // TODO: Crea el segundo widget pasando un string temporal como un rvalue
    // Pasa (name + "_temp") directamente a make_object
    
    
    // TODO: Llama 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

Practica por tu cuenta: Compilador de C++ online