Redirecționare perfectă și std::forward
Înțelegeți de ce referințele rvalue numite sunt lvalues, aflați modelul canonic de redirecționare cu std::forward și vedeți cum emplace_back folosește redirecționarea variadică pentru a construi obiecte în loc.
Problema: Referințele Rvalue numite sunt valori L
Iată problema fundamentală pe care o rezolvă redirecționarea perfectă: atunci când acceptați un argument caT&&și îl transmiteți unei alte funcții, numele îl face o valoare l:
void inner(int& x) { std::cout << "lvalue\n"; }
void inner(int&& x) { std::cout << "rvalue\n"; }
void outer(int&& x) {
inner(x); // calls inner(int&) — x is an lvalue!
}The value category is lost at every function boundary. If you are writing a wrapper function (logging, timing, factory), you need to forward the argument with its original value category preserved. Simply passing x always passes an lvalue. Using std::move(x) trece întotdeauna o valoare r. Nici unul nu este corect pentru un ambalaj generic.
std::forward: Distribuția condiționată
std::forwardeste o distribuție condiționată:
- DacăTeste o referință lvalue (dedusă deoarece a fost transmisă o valoare l),std::forwardreturnează o referință lvalue - fără cast.
- DacăTeste o non-referință (dedusă deoarece a fost transmisă o valoare r),std::forwardreturnează o referință rvalue - transformă înT&&.
Diferența cheie față destd::move:
-std::moveîntotdeauna trimite la rvalue (necondiționat).
-std::forwardtransformă în rvalue numai dacă argumentul inițial a fost un rvalue (condițional).
std::forwardar trebui folosit NUMAI cu referințe de redirecționare (T&&de unde se deduceT). Folosirea lui în altă parte este o eroare.
Modelul de redirecționare canonic
Model: acceptați o referință de redirecționareT&&, apoi utilizațistd::forwardpentru a păstra categoria valorii atunci când treceți la o altă funcție.
#include <iostream>
#include <string>
#include <utility>
#include <chrono>
// The function we want to wrap
void process(const std::string& s) {
std::cout << "process(lvalue): " << s << '\n';
}
void process(std::string&& s) {
std::cout << "process(rvalue): " << s << '\n';
// Can steal s's resources since it's an rvalue
}
// Generic wrapper that perfectly forwards
template <typename T>
void timed_process(T&& arg) {
auto start = std::chrono::steady_clock::now();
// std::forward<T>(arg) preserves the original value category
process(std::forward<T>(arg));
auto elapsed = std::chrono::steady_clock::now() - start;
std::cout << "Took: " << elapsed.count() << " ns\n";
}
int main() {
std::string name = "Alice";
timed_process(name); // T = string&, forwards as lvalue
timed_process(std::string("Bob")); // T = string, forwards as rvalue
timed_process("Charlie"); // T = const char(&)[8], forwards as lvalue
}Variadic Forwarding și emplace_back
Redirecționarea perfectă strălucește cu adevărat cu șabloane variatice.emplace_backtransmite argumentele constructorului direct în container, construind obiectul în loc, fără copii sau mișcări.
#include <iostream>
#include <vector>
#include <string>
#include <utility>
struct Employee {
std::string name;
int age;
Employee(std::string n, int a) : name(std::move(n)), age(a) {
std::cout << "Constructed: " << name << '\n';
}
Employee(const Employee&) { std::cout << "Copied\n"; }
Employee(Employee&&) noexcept { std::cout << "Moved\n"; }
};
// Custom factory using variadic forwarding
template <typename T, typename... Args>
std::vector<T> make_vector_of(Args&&... args) {
std::vector<T> v;
// Fold expression (C++17) + emplace_back
(v.emplace_back(std::forward<Args>(args)), ...);
return v;
}
int main() {
std::vector<Employee> team;
// push_back: creates temporary, then MOVES it into vector
team.push_back(Employee("Alice", 30));
// Output: Constructed: Alice, Moved
// emplace_back: constructs IN-PLACE — no copy, no move
team.emplace_back("Bob", 25);
// Output: Constructed: Bob
std::cout << "---\n";
// Variadic forwarding
auto v = make_vector_of<std::string>("hello", std::string("world"), "!");
for (const auto& s : v) std::cout << s << ' ';
std::cout << '\n';
}mișcare vs înainte: cunoașteți diferența
| |std::move|std::forward|
|---|---|---|
| Tip turnat | Distribuție necondiționată înT&&| Condițional — lvalue dacăTesteT&, rvalue dacăTesteT|
| Caz de utilizare | „Am terminat cu asta, fură” | „Treceți acest lucru împreună cu categoria sa de valoare inițială” |
| Context | Tipuri de beton, ultima utilizare a unei variabile | Numai referințe pentru redirecționarea șablonului |
| Pericol | Mutarea accidentală a unei valori de care mai aveți nevoie | Folosind fără o referință de redirecționare (fără sens) |
Regula generală: utilizațistd::moveatunci când dețineți valoarea și doriți să o oferiți. Utilizațistd::forwardcând redirecționați valoarea altcuiva.
1. Redirecționare de mai multe ori:std::forwardpoate trimite la rvalue. Dacă transmiteți același argument către două funcții, prima își poate fura resursele, lăsând gunoi pentru a doua. Trimiteți fiecare argument cel mult o dată.
2.auto&&este o referință de redirecționare:for (auto&& elem : container)deduce categoria valorii. Acest lucru este bine pentru range-for, dar aveți grijă când utilizațiauto&&în alte contexte.
3.std::forwardfără deducere:std::forwardeste doarstd::move(s)deghizat. Utilizațistd::forwardnumai cu un parametru de șablon dedus.
4. Braced-init-lists:{1, 2, 3}nu are tip și nu poate fi redirecționat. Atribuiți mai întâi unei variabile.
1. Folosițistd::forwardnumai cu referințe de redirecționare (T&&de unde se deduceT).
2. Redirecționați fiecare argument cel mult o dată — redirecționarea se poate muta, lăsând sursa goală.
3. Preferațiemplace_backfață depush_back când construiți obiecte în containere.
4. Nu transmiteți în buclă — prima iterație poate fura argumentul.
5. Utilizațiif constexprcustd::is_lvalue_reference_v atunci când aveți nevoie de un comportament diferit pentru lvalues și rvalues.
- Referințele rvalue numite sunt lvalues — categoria valorii se pierde la granițele funcției
std::forwardaruncă în mod condiționat: lvalue rămâne lvalue, rvalue rămâne rvaluestd::moveeste necondiționat (întotdeauna rvalue);std::forwardeste condiționat (păstrează originalul)emplace_backfolosește redirecționarea variadică pentru a construi obiecte în loc cu zero copii- Redirecționați fiecare argument cel mult o dată - redirecționarea poate fura resurse
Test — Verifică-ți cunoștințele
(20 XP)1. Care este diferența dintre`std::move`și`std::forward`?
2. De ce este`emplace_back`mai eficient decât`push_back`pentru a construi elemente?
3. Ce se întâmplă dacă`std::forward`același argument pentru două funcții diferite?