Menu
Coddy logo textTech

Perfect forwarding

Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 84 di 104.

Quando scrivi funzioni template che accettano argomenti e li passano ad altre funzioni, si presenta un problema: come puoi preservare il fatto che l’argomento originale fosse un lvalue o un rvalue? Il forwarding perfetto risolve il problema passando gli argomenti esattamente come sono stati ricevuti, mantenendone la categoria di valore.

Gli ingredienti chiave sono le reference di forwarding (scritte come T&& in un contesto di template) e std::forward. Una reference di forwarding può legarsi sia a lvalue sia a rvalue, e std::forward esegue un cast condizionale dell'argomento al suo tipo originale:

#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);       // Calls process(int&) - lvalue preserved
    wrapper(20);      // Calls process(int&&) - rvalue preserved
}

Senza std::forward, i parametri con nome sono sempre lvalue all'interno della funzione, anche se è stato passato un rvalue. Ciò impedirebbe alle semantiche di spostamento di funzionare correttamente quando si inoltra a costruttori o ad altre funzioni.

Il forwarding perfetto è essenziale per scrivere funzioni factory, classi wrapper e qualsiasi codice generico che debba costruire oggetti o invocare funzioni preservando l'intento del chiamante riguardo agli spostamenti rispetto alle copie.

challenge icon

Sfida

Facile

Creiamo una funzione factory che dimostri il perfect forwarding costruendo oggetti e preservando la categoria di valore degli argomenti che le vengono passati. Creerai una funzione generica make_object che inoltra gli argomenti al costruttore di una classe, assicurandosi che gli lvalue vengano copiati e gli rvalue spostati.

Organizzerai il codice in tre file:

  • Widget.h: definisci una classe Widget che tiene traccia di come riceve i suoi dati.

    La tua classe Widget deve memorizzare un nome std::string e un valore int. Fornisci due costruttori:

    • Uno che accetta const std::string& (riferimento lvalue) e int: stampa Widget constructed (copy): [name]
    • Uno che accetta std::string&& (riferimento rvalue) e int: stampa Widget constructed (move): [name] e sposta la stringa

    Aggiungi anche un metodo display() che stampa [name]: [value].

  • Factory.h: crea la tua funzione factory con perfect forwarding.

    Scrivi una funzione template make_object che accetti un riferimento di forwarding per il nome e un normale int per il valore. Usa std::forward per passare l'argomento nome al costruttore di Widget, preservando la sua categoria di valore originale.

    La funzione deve restituire il Widget costruito per valore.

  • main.cpp: leggi due input:
    1. Un nome (stringa)
    2. Un valore (intero)

    Dimostra il perfect forwarding creando due widget:

    1. Per prima cosa, memorizza il nome in una variabile locale e passalo a make_object come lvalue
    2. In secondo luogo, passa direttamente a make_object come rvalue una stringa temporanea (lo stesso nome concatenato con "_temp")

    Dopo aver creato ciascun widget, chiama il suo metodo display().

Ad esempio, con gli input Gadget e 42:

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

Con gli input Device e 100:

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

L'idea fondamentale è che la funzione make_object non sa se ha ricevuto un lvalue o un rvalue, ma usando std::forward preserva questa informazione quando chiama il costruttore di Widget. Questo consente alla risoluzione degli overload del costruttore di selezionare la versione appropriata, permettendo spostamenti efficienti per gli oggetti temporanei e copiando in sicurezza le variabili con nome.

Provalo tu

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

using namespace std;

int main() {
    // Leggi l'input
    string name;
    int value;
    cin >> name >> value;
    
    // TODO: Crea il primo widget passando name come lvalue
    // Memorizza name in una variabile locale e passala a make_object
    
    
    // TODO: Chiama display() sul primo widget
    
    
    // TODO: Crea il secondo widget passando una stringa temporanea come rvalue
    // Passa (name + "_temp") direttamente a make_object
    
    
    // TODO: Chiama display() sul secondo widget
    
    
    return 0;
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore C++ online