Menu
Coddy logo textTech

Perfektes Forwarding

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 84 von 104.

Beim Schreiben von Template-Funktionen, die Argumente akzeptieren und sie an andere Funktionen weitergeben, tritt ein Problem auf: Wie bewahrst du, ob das ursprüngliche Argument ein Lvalue oder ein Rvalue war? Perfektes Weiterleiten löst dies, indem Argumente genau so weitergegeben werden, wie sie empfangen wurden, wobei ihre Wertkategorie erhalten bleibt.

Die entscheidenden Bestandteile sind Weiterleitungsreferenzen (in einem Template-Kontext als T&& geschrieben) und std::forward. Eine Weiterleitungsreferenz kann sowohl an Lvalues als auch an Rvalues gebunden werden, und std::forward wandelt das Argument bedingt zurück in seinen ursprünglichen Typ:

#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 erhalten
    wrapper(20);      // Ruft process(int&&) auf – rvalue erhalten
}

Ohne std::forward sind benannte Parameter innerhalb der Funktion immer Lvalues, selbst wenn ein Rvalue übergeben wurde. Dies würde verhindern, dass Move-Semantik beim Weiterleiten an Konstruktoren oder andere Funktionen korrekt funktioniert.

Perfektes Weiterleiten ist unerlässlich für das Schreiben von Factory-Funktionen, Wrapper-Klassen und generischem Code, der Objekte konstruieren oder Funktionen aufrufen muss, während die Absicht des Aufrufers hinsichtlich Verschiebungen gegenüber Kopien erhalten bleibt.

challenge icon

Aufgabe

Einfach

Wir erstellen eine Factory-Funktion, die Perfect Forwarding demonstriert, indem sie Objekte konstruiert und dabei die Wertkategorie der an sie übergebenen Argumente beibehält. Du erstellst eine generische Funktion make_object, die Argumente an einen Klassenkonstruktor weiterleitet und sicherstellt, dass Lvalues kopiert und Rvalues verschoben werden.

Du organisierst deinen Code über drei Dateien:

  • Widget.h: Definiere eine Widget-Klasse, die nachverfolgt, wie sie ihre Daten erhält.

    Dein Widget sollte einen std::string-Namen und einen int-Wert speichern. Stelle zwei Konstruktoren bereit:

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

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

  • Factory.h: Erstelle deine Factory-Funktion für Perfect Forwarding.

    Schreibe eine Template-Funktion make_object, die eine Forwarding-Referenz für den Namen und einen regulären int für den Wert akzeptiert. Verwende std::forward, um das Namensargument an den Widget-Konstruktor zu übergeben und dabei seine ursprüngliche Wertkategorie beizubehalten.

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

  • main.cpp: Lies zwei Eingaben ein:
    1. Einen Namen (String)
    2. Einen Wert (Ganzzahl)

    Demonstriere Perfect Forwarding, indem du zwei Widgets erstellst:

    1. Speichere zuerst den Namen in einer lokalen Variable und übergib ihn als Lvalue an make_object
    2. Übergib zweitens direkt einen temporären String (denselben Namen, verkettet mit "_temp") als Rvalue an make_object

    Rufe nach dem Erstellen jedes Widgets seine Methode display() 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 zentrale Erkenntnis ist, dass deine Funktion make_object nicht weiß, ob sie ein Lvalue oder Rvalue erhalten hat. Durch die Verwendung von std::forward bewahrt sie diese Information jedoch beim Aufruf des Widget-Konstruktors. Dadurch kann die Überladungsauflösung des Konstruktors die passende Version auswählen, was effiziente Verschiebungen für temporäre Objekte ermöglicht und gleichzeitig benannte Variablen sicher kopiert.

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: Erstelle das erste Widget, indem name als L-Wert übergeben wird
    // Speichere den Namen in einer lokalen Variable und übergebe ihn an make_object
    
    
    // TODO: Rufe display() auf dem ersten Widget auf
    
    
    // TODO: Erstelle das zweite Widget, indem ein temporärer String als R-Wert übergeben wird
    // Übergebe (name + "_temp") direkt an make_object
    
    
    // TODO: Rufe display() auf dem zweiten Widget auf
    
    
    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

Übe selbstständig: Online-C++-Compiler