Direct Pass (C++)

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] .

Fundal

Comportament special al parametrilor - Legături temporare

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.

Lipirea link -urilor

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...

  • dacă T=șir, vaObj(string&&)
  • dacă T=șir&, vaObj(string&)
  • dacă T=șir constant&, va fiObj(const string&)

Consecință: nu este posibil să știi automat dacă un link este temporar

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.

  • T=șir, instanțiat la , în interiorul x=șir&.Obj(string&&)
  • T=șir&, instanțiat la , în interiorul x=șir&.Obj(string&)
  • T=const șir&, instanțiat la , în interiorul x=const șir&.Obj(const string&)

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.

Soluție: std::forward

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

Idioma „după valoare + mutare”

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.

Note

  1. 1 2 Vandewoerd, 2018 , 6.1 Live, p. 125.
  2. Horton, 2014 , Perfect Forwarding, p. 373.
  3. std::forward Arhivat la 19 ianuarie 2019 la referința Wayback Machine C++
  4. Vandewoerd 2018 , 15.6.3 Live, p. 333.
  5. Vandewoerd 2018 , 15.6.3 Live, p. 332.
  6. Vandewoerd, 2018 , 15.6.2 Legături transferabile, p. 331.
  7. Vandewoerd, 2018 , 6.1 Live, p. 127.

Surse

  • D. Vandevoerd, N. Josattis, D. Gregor. Șabloane C++. Referința dezvoltatorului = șabloane C++. Ghidul complet. - al 2-lea. - Sankt Petersburg.  : „Alfa-carte”, 2018. - 848 p. - ISBN 978-5-9500296-8-4 .
  • I. Horton. Ivor Horton's Beginning Visual C++ ® 2013. - John Wiley & Sons, Inc., 2014. - ISBN 978-1-118-84577-6 .

Link -uri