A közvetlen átvitel ( Eng. Perfect Forwarding ) egy idiomatikus mechanizmus paraméterattribútumok átvitelére a C++ nyelv általánosított kódjának eljárásaiban . A C++11 kiadásban szabványosították STL funkcionalitással és továbbítási hivatkozások szintaxisával , és egységesítették a variadic sablonokkal való használatra [1] [2] .
Közvetlen átadást használunk, ha az általános kód függvényei és eljárásai szükségesek ahhoz, hogy paraméterezett argumentumaik alapvető tulajdonságait változatlanul hagyják, azaz [1] :
A közvetlen átadás gyakorlati megvalósítása a nyelvi szabványban std::forwarda fejlécfájlból <utility>[3] [4] egy függvény segítségével valósul meg . Ennek eredményeként a -hivatkozások speciális következtetési szabályainak &&és hajtogatásának kombinációja lehetővé teszi egy olyan funkcionális sablon létrehozását, amely tetszőleges argumentumokat fogad el, azok típusának és alapvető tulajdonságainak rögzítésével ( rvalue vagy lvalue ). Ennek az információnak a mentése előre meghatározza az argumentumok átadásának lehetőségét más függvények és metódusok hívásakor [5] .
Tekintsük az elemi objektumot két konstruktorral – az egyik egy mezőt másol az std::stringből, a második mozog.
class Obj { nyilvános : Obj ( const std :: string & x ) : mező ( x ) {} Obj ( std :: string && x ) : mező ( std :: mozgatás ( x )) {} // std::mozgatás szükséges!! privát : std :: stringfield ; _ }Az első konstruktor túlterhelés a leggyakoribb a C++03-ban. És a második std-ben:: mozogj, és ezért.
A kívül található string&& paraméter ideiglenes (rvalue) hivatkozás, és egy elnevezett (lvalue) objektum átadása nem lehetséges. A függvényen belül pedig ennek a paraméternek a neve (lvalue), azaz string&. Ez a biztonság kedvéért történik: ha egy függvény, amely egy string&& karakterláncot vesz fel, összetett adatkezelésen megy keresztül, lehetetlen véletlenül megsemmisíteni a string&& paramétert.
A kérdések akkor kezdődnek, amikor sok a paraméter - 4, 8, 16 ... konstruktort kell készítenie.
osztály Obj2 { nyilvános : Obj ( const std :: string & x1 , const std :: string & x2 ) : field1 ( x1 ), field2 ( x2 ) {} Obj ( const std :: string & x1 , std :: string && x2 ) : field1 ( x1 ), field2 ( std :: move ( x2 )) {} // ...és további két privát túlterhelés : std :: string mező1 , mező2 ; }Kétféleképpen nem lehet entitásokat szorozni, az "érték+mozgás" idióma és a metaprogramozás , utóbbihoz pedig egy második C++11 mechanizmus is készült.
A hivatkozás összeomlását ez a kód magyarázza a legjobban .
a One = int && ; használva Kettő = Egy & ; // majd Kettő = int&Az átadott referenciákhoz való átadáskor nem csak a függvénynek átadott paraméter típusát derítik ki, hanem azt is értékelik, hogy rvalue vagy lvalue . Ha a függvénynek átadott paraméter egy lvvalue , akkor a behelyettesített érték egyben az lvalue hivatkozása is lesz . Ennek ellenére meg kell jegyezni, hogy egy sablonparaméter-típus &&-referenciaként való deklarálása érdekes mellékhatásokkal járhat. Például szükségessé válik egy adott típusú összes helyi változóhoz explicit inicializátor megadása, mivel ha ezeket lvvalue paraméterekkel használjuk, a típuskövetkeztetés a sablon példányosítása után egy lvvalue hivatkozás értékét rendeli hozzájuk, ami a nyelvi követelménytől függően rendelkeznie kell inicializálóval [6] .
A linkragasztás a következő mintákat teszi lehetővé:
class Obj { nyilvános : sablon < classT > _ Obj ( T && x ) : mező ( std :: előre < T > ( x )) {} // ugorj előre és csináld jól privát : // alább elmagyarázzuk, miért nem tudod megtenni explicit forward függvény nélkül std :: string mező ; }Az ilyen ideiglenes hivatkozásokhoz a fordítók speciális szabályokat adtak hozzá [7] , amelyek miatt…
Térjünk vissza az Obj::Obj sablonkonstruktorhoz. Ha nem veszi figyelembe az idegen típusokat, hanem csak a karakterláncot, akkor három lehetőség közül választhat.
A harmadik lehetőség jó, de az egyszerű típuskövetkeztetés nem tudja megkülönböztetni az elsőt a másodiktól. De az első változatban a std::move szükséges a maximális teljesítményhez, a másodikban veszélyes: a mozgással történő hozzárendelés „kibelezi” a karakterláncot, ami még hasznos lehet.
Térjünk vissza a sablonkonstruktorunkhoz.
sablon < classT > _ Obj ( T && x ) : mező ( std :: előre < T > ( x )) {}A sablon csak sablonokban használatos (nem sablon kódban van elég ). Megköveteli, hogy a típust kifejezetten meg kell adni (egyébként nem különböztethető meg a -tól ), és vagy nem csinál semmit, vagy -re bővül . std::forwardstd::moveObj(string&&)Obj(string&)std::move
A második módja annak, hogy ne szorozzuk meg az entitásokat: a paraméter érték alapján kerül átadásra . std::move
class Obj { nyilvános : Obj ( std :: karakterlánc x ) : mező ( std :: mozgatás ( x )) {} privát : std :: stringfield ; _ }Amikor egy objektum mozgatása lényegesen "könnyebb", mint a másolás, általában nem sablon kódban.