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.
Wyzwanie
ŁatwyStwó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
Widgetpowinna przechowywać nazwę typustd::stringi wartość typuint. Dodaj dwa konstruktory:- Jeden przyjmujący
const std::string&(referencję do l-wartości) iint: wyświetlaWidget constructed (copy): [name] - Jeden przyjmujący
std::string&&(referencję do r-wartości) iint: wyświetlaWidget constructed (move): [name]i przenosi ciąg znaków
Dodaj też metodę
display(), która wyświetla[name]: [value].- Jeden przyjmujący
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łyintdla wartości. Użyjstd::forward, aby przekazać argument nazwy do konstruktoraWidget, zachowując jego pierwotną kategorię wartości.Funkcja powinna zwracać skonstruowany obiekt
Widgetprzez wartość.main.cpp: Wczytaj dwa dane wejściowe:- Nazwę (ciąg znaków)
- Wartość (liczbę całkowitą)
Zademonstruj doskonałe przekazywanie, tworząc dwa obiekty widgetów:
- Najpierw zapisz nazwę w zmiennej lokalnej i przekaż ją do
make_objectjako l-wartość - Następnie przekaż tymczasowy ciąg znaków (tę samą nazwę połączoną z
"_temp") bezpośrednio domake_objectjako 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: 42Dla danych wejściowych Device i 100:
Widget constructed (copy): Device
Device: 100
Widget constructed (move): Device_temp
Device_temp: 100Kluczowa 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;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generyczny12Nowoczesne funkcje C++
Semantyka przenoszenia i r-wartościDoskonałe przekazywanieWyrażenia lambda w szczegółachstd::function i std::bindconstexpr i constevalWiązania strukturalneoptional, variant, anyPoćwicz samodzielnie: Kompilator C++ online