Transferul direct ( Eng. Perfect Forwarding ) este un mecanism idiomatic pentru transferul atributelor parametrilor în procedurile codului generalizat al limbajului C++ . A fost standardizat în ediția C++11 cu funcționalitate STL și sintaxa referințelor de redirecționare și unificat pentru utilizare cu șabloane variadice [1] [2] .
Transmiterea directă este utilizată atunci când funcțiile și procedurile de cod generic sunt necesare pentru a lăsa proprietățile fundamentale ale argumentelor lor parametrizate neschimbate, adică [1] :
Implementarea practică a trecerii directe în standardul limbajului este implementată folosind o funcție std::forwarddin fișierul antet <utility>[3] [4] . Ca rezultat, combinația de reguli speciale de inferență pentru &&-references și plierea lor vă permite să creați un șablon funcțional care acceptă argumente arbitrare cu fixarea tipurilor și proprietăților lor de bază ( rvalue sau lvalue ). Salvarea acestor informații predetermina capacitatea de a trece aceste argumente atunci când se apelează alte funcții și metode [5] .
Să luăm în considerare obiectul elementar cu doi constructori — unul copiază un câmp din std::string, al doilea se mută.
clasa Obj { public : Obj ( const std :: șir & x ) : câmp ( x ) {} Obj ( std :: șir && x ) : câmp ( std :: mutare ( x )) {} // std:: mutare necesară!! privat : std :: stringfield ; _ }Prima supraîncărcare a constructorului este cea mai comună dintre C++03. Si in a doua std:: muta, si de aia.
Parametrul șir&& din exterior este o referință temporară (rvalue), iar trecerea unui obiect numit (lvalue) nu este posibilă. Și în interiorul funcției, acest parametru este numit (lvalue), adică șir&. Acest lucru se face pentru siguranță: dacă o funcție care ia un șir&& suferă manipulări complexe de date, este imposibil să distrugi accidental parametrul șir&&.
Întrebările încep când există o mulțime de parametri - trebuie să faci 4, 8, 16 ... constructori.
clasa Obj2 { public : Obj ( const std :: string & x1 , const std :: string & x2 ) : field1 ( x1 ), field2 ( x2 ) {} Obj ( const std :: șir & x1 , std :: șir && x2 ) : câmp1 ( x1 ), câmp2 ( std :: mutare ( x2 )) {} // ...și încă două supraîncărcări private : std :: șir câmp1 , câmp2 ; }Există două modalități de a nu multiplica entitățile, expresia „by-value+move” și metaprogramarea , iar pentru aceasta din urmă a fost realizat un al doilea mecanism C++11.
Restrângerea referințelor este explicată cel mai bine prin acest cod .
folosind One = int && ; folosind Doi = Unu & ; // apoi Doi = int&La trecerea la referințele trecute, nu se află doar tipul parametrului transmis funcției, ci se oferă și o evaluare dacă este o valoare r sau o valoare l . Dacă parametrul transmis funcției este o valoare l, atunci valoarea înlocuită va fi, de asemenea, o referință la valoarea l . Acestea fiind spuse, se observă că declararea unui tip de parametru șablon ca &&referință poate avea efecte secundare interesante. De exemplu, devine necesar să se specifice în mod explicit inițializatori pentru toate variabilele locale de un anumit tip, deoarece atunci când sunt utilizate cu parametrii lvalue , tip inferență după instanțierea șablonului le va atribui valoarea unei referințe lvalue , care, prin cerința de limbă, trebuie să aibă un inițializator [6] .
Lipirea legăturii permite următoarele modele:
clasa Obj { public : șablon < classT > _ Obj ( T && x ) : câmp ( std :: forward < T > ( x )) {} // sări înainte și face-o drept privat : // mai jos explicăm de ce nu poți face asta fără o funcție explicită de forward std :: câmp șir ; }Pentru astfel de referințe temporare, compilatorii au adăugat reguli speciale [7] , din cauza cărora...
Să revenim la constructorul de șablon Obj::Obj. Dacă nu luați în considerare tipurile străine, ci doar șir, sunt posibile trei opțiuni.
A treia opțiune este în regulă, dar inferența de tip simplu nu poate distinge prima opțiune de a doua. Dar în prima variantă, std::move este necesar pentru o performanță maximă, în a doua este periculos: alocarea cu o mișcare va „evidenția” șirul, care poate fi totuși util.
Să revenim la constructorul nostru de șabloane.
șablon < classT > _ Obj ( T && x ) : câmp ( std :: înainte < T > ( x )) {}Șablonul este folosit doar în șabloane (există suficient în codul non-șablon ). Necesită ca tipul să fie specificat în mod explicit (în caz contrar, nu se poate distinge de ) și fie nu face nimic, fie se extinde la . std::forwardstd::moveObj(string&&)Obj(string&)std::move
A doua modalitate de a nu multiplica entitățile: parametrul este luat după valoare și transmis prin . std::move
clasa Obj { public : Obj ( std :: șir x ) : câmp ( std :: mutare ( x )) {} privat : std :: stringfield ; _ }Folosit atunci când mutați un obiect este semnificativ mai „ușor” decât copierea, de obicei în cod non-șablon.