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 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.

challenge icon

Aufgabe

Einfach

Lassen 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 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& (L-Wert-Referenz) und int entgegennimmt — gibt Widget constructed (copy): [name] aus
    • Einen, der std::string&& (R-Wert-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 Wertkategorie beizubehalten.

    Die Funktion sollte das konstruierte Widget als Wert 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 L-Wert an make_object
    2. Übergeben Sie zweitens einen temporären String (denselben Namen verkettet mit "_temp") direkt als R-Wert 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 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;
}
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