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 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 lvalues, selbst wenn ein rvalue übergeben wurde. Dies würde verhindern, dass die Move-Semantik beim Weiterleiten an Konstruktoren oder andere Funktionen korrekt funktioniert.
Perfect Forwarding ist essenziell 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 Verschiebe- gegenüber Kopieroperationen beibehalten wird.
Aufgabe
EinfachLassen Sie uns eine Factory-Funktion erstellen, die Perfect Forwarding demonstriert, indem sie Objekte konstruiert und dabei die 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 Lvalues kopiert und Rvalues verschoben werden.
Sie werden Ihren Code über 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&(Lvalue-Referenz) undintentgegennimmt — gibtWidget constructed (copy): [name]aus - Einen, der
std::string&&(Rvalue-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 Value Category beizubehalten.Die Funktion sollte das konstruierte
Widgetper Value 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 Lvalue an
make_object - Übergeben Sie zweitens einen temporären String (denselben Namen verkettet mit
"_temp") direkt als Rvalue 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 Lvalue oder Rvalue 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 Moves für temporäre Objekte ermöglicht, während benannte Variablen sicher kopiert werden.
Spickzettel
Perfect Forwarding ermöglicht es Template-Funktionen, Argumente an andere Funktionen zu übergeben und dabei deren ursprüngliche Wertkategorie (Lvalue oder Rvalue) beizubehalten.
Der Mechanismus nutzt Forwarding-Referenzen (T&& im Template-Kontext) in Kombination mit std::forward:
#include <utility>
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg));
}Eine Forwarding-Referenz kann sowohl an Lvalues als auch an Rvalues binden. std::forward<T> führt einen bedingten Cast des Arguments zurück in seinen ursprünglichen Typ durch.
Ohne std::forward werden benannte Parameter innerhalb einer Funktion immer als Lvalues behandelt, selbst wenn ein Rvalue übergeben wurde. Dies würde verhindern, dass die Move-Semantik korrekt funktioniert.
Beispiel zur Demonstration der Beibehaltung der Wertkategorie:
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); // Ruft process(int&) auf - Lvalue beibehalten
wrapper(20); // Ruft process(int&&) auf - Rvalue beibehaltenPerfect Forwarding ist essenziell für Factory-Funktionen, Wrapper-Klassen und generischen Code, der Objekte konstruiert oder Funktionen aufruft und dabei die Absicht des Aufrufers hinsichtlich Moves versus Kopien beibehält.
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 name als lvalue übergeben wird
// Den name in einer lokalen Variable speichern und an make_object übergeben
// TODO: display() für das erste Widget aufrufen
// TODO: Zweiten Widget erstellen, indem ein temporärer string als rvalue übergeben wird
// Pass (name + "_temp") direkt an make_object übergeben
// 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