Perfect Forwarding
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 84 von 104.
Beim Schreiben von Template-Funktionen, die Argumente entgegennehmen und an andere Funktionen weitergeben, tritt ein Problem auf: Wie bewahrt man, ob das ursprüngliche Argument ein lvalue oder ein rvalue war? Perfect Forwarding löst dies, indem Argumente genau so weitergegeben werden, wie sie empfangen wurden, wobei ihre Wertkategorie beibehalten wird.
Die entscheidenden Bestandteile sind Forwarding-Referenzen (geschrieben als T&& in einem Template-Kontext) und std::forward. Eine Forwarding-Referenz kann sowohl an L-Werte als auch an R-Werte binden, und std::forward führt einen bedingten Cast des Arguments zurück in seinen ursprünglichen Typ durch:
#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); // Ruft process(int&) auf - lvalue bleibt erhalten
wrapper(20); // Ruft process(int&&) auf - rvalue bleibt erhalten
}Ohne std::forward sind benannte Parameter innerhalb der Funktion immer L-Werte, selbst wenn ein R-Wert übergeben wurde. Dies würde verhindern, dass die Move-Semantik beim Weiterleiten an Konstruktoren oder andere Funktionen korrekt funktioniert.
Perfect Forwarding ist unerlässlich für das Schreiben von Factory-Funktionen, Wrapper-Klassen und jeglichem generischen Code, der Objekte konstruieren oder Funktionen aufrufen muss, während die Absicht des Aufrufers in Bezug auf Moves gegenüber Kopien beibehalten wird.
Aufgabe
EinfachLassen Sie uns eine Factory-Funktion erstellen, die Perfect Forwarding demonstriert, indem sie Objekte konstruiert und dabei die Wertkategorie (Value Category) der an sie übergebenen Argumente beibehält. Sie werden eine generische make_object-Funktion erstellen, die Argumente an einen Klassenkonstruktor weiterleitet und so sicherstellt, dass L-Werte kopiert und R-Werte verschoben werden.
Sie werden Ihren Code in drei Dateien organisieren:
Widget.h: Definieren Sie eineWidget-Klasse, die verfolgt, wie sie ihre Daten erhält.Ihr
Widgetsollte einenstd::string-Namen und einenint-Wert speichern. Stellen Sie zwei Konstruktoren bereit:- Einen, der
const std::string&(L-Wert-Referenz) undintentgegennimmt — gibtWidget constructed (copy): [name]aus - Einen, der
std::string&&(R-Wert-Referenz) undintentgegennimmt — gibtWidget constructed (move): [name]aus und verschiebt den String
Fügen Sie außerdem eine
display()-Methode hinzu, die[name]: [value]ausgibt.- Einen, der
Factory.h: Erstellen Sie Ihre Perfect Forwarding Factory-Funktion.Schreiben Sie eine Template-Funktion
make_object, die eine Forwarding-Referenz für den Namen und ein reguläresintfür den Wert akzeptiert. Verwenden Siestd::forward, um das Namensargument an denWidget-Konstruktor zu übergeben und dabei seine ursprüngliche Wertkategorie beizubehalten.Die Funktion sollte das konstruierte
Widgetals Wert zurückgeben.main.cpp: Lesen Sie zwei Eingaben ein:- Einen Namen (String)
- Einen Wert (Integer)
Demonstrieren Sie Perfect Forwarding, indem Sie zwei Widgets erstellen:
- Speichern Sie zuerst den Namen in einer lokalen Variable und übergeben Sie ihn als L-Wert an
make_object - Übergeben Sie zweitens einen temporären String (denselben Namen verkettet mit
"_temp") direkt als R-Wert anmake_object
Rufen Sie nach der Erstellung jedes Widgets dessen
display()-Methode auf.
Zum Beispiel mit den Eingaben Gadget und 42:
Widget constructed (copy): Gadget
Gadget: 42
Widget constructed (move): Gadget_temp
Gadget_temp: 42Mit den Eingaben Device und 100:
Widget constructed (copy): Device
Device: 100
Widget constructed (move): Device_temp
Device_temp: 100Die entscheidende Erkenntnis ist, dass Ihre make_object-Funktion nicht weiß, ob sie einen L-Wert oder einen R-Wert erhalten hat – aber durch die Verwendung von std::forward behält sie diese Information beim Aufruf des Widget-Konstruktors bei. Dies ermöglicht es der Überladungsauflösung des Konstruktors, die entsprechende Version auszuwählen, was effiziente Verschiebungen für temporäre Objekte ermöglicht, während benannte Variablen sicher kopiert werden.
Probier es selbst
#include <iostream>
#include <string>
#include "Widget.h"
#include "Factory.h"
using namespace std;
int main() {
// Eingabe lesen
string name;
int value;
cin >> name >> value;
// TODO: Ersten Widget erstellen, indem der Name als L-Wert übergeben wird
// Speichere den Namen in einer lokalen Variable und übergib ihn an make_object
// TODO: display() für das erste Widget aufrufen
// TODO: Zweiten Widget erstellen, indem ein temporärer String als R-Wert übergeben wird
// Übergib (name + "_temp") direkt an make_object
// TODO: display() für das zweite Widget aufrufen
return 0;
}Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container12Moderne C++ Features
Move-Semantik & RvaluesPerfect ForwardingLambda-Ausdrücke im Detailstd::function & std::bindconstexpr und constevalStructured Bindingsoptional, variant, any