Menu
Coddy logo textTech

Doskonałe przekazywanie

Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 84 z 104.

Podczas pisania funkcji szablonowych, które przyjmują argumenty i przekazują je do innych funkcji, pojawia się problem: jak zachować informację, czy oryginalny argument był l-wartością czy r-wartością? Przekazywanie doskonałe rozwiązuje ten problem, przekazując argumenty dokładnie w takiej postaci, w jakiej zostały otrzymane, i zachowując ich kategorię wartości.

Kluczowymi składnikami są referencje przekazujące (zapisywane jako T&& w kontekście szablonu) oraz std::forward. Referencja przekazująca może wiązać się zarówno z l-wartościami, jak i r-wartościami, a std::forward warunkowo rzutuje argument z powrotem na jego pierwotny 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);       // Wywołuje process(int&) – zachowana kategoria lvalue
    wrapper(20);      // Wywołuje process(int&&) – zachowana kategoria rvalue
}

Bez std::forward nazwane parametry są zawsze lwartościami wewnątrz funkcji, nawet jeśli przekazano r-wartość. Uniemożliwiłoby to prawidłowe działanie semantyki przenoszenia podczas przekazywania do konstruktorów lub innych funkcji.

Perfekcyjne przekazywanie jest niezbędne przy pisaniu funkcji fabrykujących, klas opakowujących i dowolnego kodu ogólnego, który musi tworzyć obiekty lub wywoływać funkcje, zachowując intencje wywołującego dotyczące przenoszenia i kopiowania.

challenge icon

Wyzwanie

Łatwy

Stwórzmy funkcję fabrykującą, która demonstruje doskonałe przekazywanie, tworząc obiekty z zachowaniem kategorii wartości przekazanych argumentów. Utworzysz generyczną funkcję make_object, która przekazuje argumenty do konstruktora klasy, zapewniając kopiowanie l-wartości i przenoszenie r-wartości.

Rozmieścisz kod w trzech plikach:

  • Widget.h: Zdefiniuj klasę Widget, która śledzi sposób otrzymywania danych.

    Klasa Widget powinna przechowywać nazwę typu std::string i wartość typu int. Dodaj dwa konstruktory:

    • Jeden przyjmujący const std::string& (referencję do l-wartości) i int: wyświetla Widget constructed (copy): [name]
    • Jeden przyjmujący std::string&& (referencję do r-wartości) i int: wyświetla Widget constructed (move): [name] i przenosi ciąg znaków

    Dodaj też metodę display(), która wyświetla [name]: [value].

  • Factory.h: Utwórz funkcję fabrykującą z doskonałym przekazywaniem.

    Napisz funkcję szablonową make_object, która przyjmuje referencję przekazującą dla nazwy oraz zwykły int dla wartości. Użyj std::forward, aby przekazać argument nazwy do konstruktora Widget, zachowując jego pierwotną kategorię wartości.

    Funkcja powinna zwracać skonstruowany obiekt Widget przez wartość.

  • main.cpp: Wczytaj dwa dane wejściowe:
    1. Nazwę (ciąg znaków)
    2. Wartość (liczbę całkowitą)

    Zademonstruj doskonałe przekazywanie, tworząc dwa obiekty widgetów:

    1. Najpierw zapisz nazwę w zmiennej lokalnej i przekaż ją do make_object jako l-wartość
    2. Następnie przekaż tymczasowy ciąg znaków (tę samą nazwę połączoną z "_temp") bezpośrednio do make_object jako r-wartość

    Po utworzeniu każdego obiektu widgetu wywołaj jego metodę display().

Na przykład, dla danych wejściowych Gadget i 42:

Widget constructed (copy): Gadget
Gadget: 42
Widget constructed (move): Gadget_temp
Gadget_temp: 42

Dla danych wejściowych Device i 100:

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

Kluczowa kwestia jest taka, że funkcja make_object nie wie, czy otrzymała l-wartość, czy r-wartość, ale dzięki użyciu std::forward zachowuje tę informację podczas wywoływania konstruktora Widget. Dzięki temu wybór przeciążenia konstruktora wskazuje odpowiednią wersję, umożliwiając wydajne przenoszenie obiektów tymczasowych i bezpieczne kopiowanie nazwanych zmiennych.

Spróbuj swoich sił

#include <iostream>
#include <string>
#include "Widget.h"
#include "Factory.h"

using namespace std;

int main() {
    // Wczytaj dane wejściowe
    string name;
    int value;
    cin >> name >> value;
    
    // TODO: Utwórz pierwszy widżet, przekazując name jako lvalue
    // Zapisz nazwę w zmiennej lokalnej i przekaż ją do make_object
    
    
    // TODO: Wywołaj display() na pierwszym widżecie
    
    
    // TODO: Utwórz drugi widżet, przekazując tymczasowy łańcuch znaków jako rvalue
    // Przekaż (name + "_temp") bezpośrednio do make_object
    
    
    // TODO: Wywołaj display() na drugim widżecie
    
    
    return 0;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online