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.
Sfida
FacileCreiamo 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 classeWidgetche tiene traccia di come riceve i suoi dati.La tua classe
Widgetdeve memorizzare un nomestd::stringe un valoreint. Fornisci due costruttori:- Uno che accetta
const std::string&(riferimento lvalue) eint: stampaWidget constructed (copy): [name] - Uno che accetta
std::string&&(riferimento rvalue) eint: stampaWidget constructed (move): [name]e sposta la stringa
Aggiungi anche un metodo
display()che stampa[name]: [value].- Uno che accetta
Factory.h: crea la tua funzione factory con perfect forwarding.Scrivi una funzione template
make_objectche accetti un riferimento di forwarding per il nome e un normaleintper il valore. Usastd::forwardper passare l'argomento nome al costruttore diWidget, preservando la sua categoria di valore originale.La funzione deve restituire il
Widgetcostruito per valore.main.cpp: leggi due input:- Un nome (stringa)
- Un valore (intero)
Dimostra il perfect forwarding creando due widget:
- Per prima cosa, memorizza il nome in una variabile locale e passalo a
make_objectcome lvalue - In secondo luogo, passa direttamente a
make_objectcome 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: 42Con gli input Device e 100:
Widget constructed (copy): Device
Device: 100
Widget constructed (move): Device_temp
Device_temp: 100L'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;
}
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
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore generico12Funzionalità moderne di C++
Semantica di spostamento e rvaluePerfect forwardingEspressioni lambda in dettagliostd::function e std::bindconstexpr e constevalStructured bindingsoptional, variant, anyEsercitati da solo: Compilatore C++ online