Menu
Coddy logo textTech

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.

challenge icon

Aufgabe

Einfach

Lassen 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 eine Widget-Klasse, die verfolgt, wie sie ihre Daten erhält.

    Ihr Widget sollte einen std::string-Namen und einen int-Wert speichern. Stellen Sie zwei Konstruktoren bereit:

    • Einen, der const std::string& (Lvalue-Referenz) und int entgegennimmt — gibt Widget constructed (copy): [name] aus
    • Einen, der std::string&& (Rvalue-Referenz) und int entgegennimmt — gibt Widget constructed (move): [name] aus und verschiebt den String

    Fügen Sie außerdem eine display()-Methode hinzu, die [name]: [value] ausgibt.

  • 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äres int für den Wert akzeptiert. Verwenden Sie std::forward, um das Namensargument an den Widget-Konstruktor zu übergeben und dabei seine ursprüngliche Value Category beizubehalten.

    Die Funktion sollte das konstruierte Widget per Value zurückgeben.

  • main.cpp: Lesen Sie zwei Eingaben ein:
    1. Einen Namen (String)
    2. Einen Wert (Integer)

    Demonstrieren Sie Perfect Forwarding, indem Sie zwei Widgets erstellen:

    1. Speichern Sie zuerst den Namen in einer lokalen Variable und übergeben Sie ihn als Lvalue an make_object
    2. Übergeben Sie zweitens einen temporären String (denselben Namen verkettet mit "_temp") direkt als Rvalue an make_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: 42

Mit den Eingaben Device und 100:

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

Die 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 beibehalten

Perfect 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;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung