Direkt överföring ( Eng. Perfect Forwarding ) är en idiomatisk mekanism för att överföra parameterattribut i procedurer för den generaliserade koden för C++-språket . Det har standardiserats i C++11- utgåvan med STL- funktionalitet och syntax för vidarebefordran av referenser , och har förenats för användning med variatiska mallar [1] [2] .
Direktöverföring används när funktioner och procedurer för generisk kod krävs för att lämna de grundläggande egenskaperna för deras parameteriserade argument oförändrade, det vill säga [1] :
Den praktiska implementeringen av direktpassning i språkstandarden implementeras med hjälp av en funktion std::forwardfrån headerfilen <utility>[3] [4] . Som ett resultat låter kombinationen av speciella slutledningsregler för &&-referenser och deras vikning dig skapa en funktionell mall som accepterar godtyckliga argument med att fixa deras typer och grundläggande egenskaper ( rvalue eller lvalue ). Att spara denna information förutbestämmer möjligheten att skicka dessa argument när andra funktioner och metoder anropas [5] .
Låt oss betrakta det elementära objektet med två konstruktorer - en kopierar ett fält från std::string, den andra flyttar.
klass Obj { offentliga : Obj ( const std :: string & x ) : field ( x ) {} Obj ( std :: sträng && x ) : fält ( std :: flytta ( x )) {} // std::flyttning behövs!! privat : std :: stringfield ; _ }Den första konstruktoröverbelastningen är den vanligaste av C++03. Och i den andra std:: flytta, och det är därför.
Parametern string&& utanför är en tillfällig (rvalue) referens, och det är inte möjligt att skicka ett namngivet (lvalue) objekt. Och inuti funktionen heter denna parameter (lvalue), det vill säga string&. Detta görs för säkerhets skull: om en funktion som tar en sträng&& genomgår komplexa datamanipulationer, är det omöjligt att av misstag förstöra parametern string&&.
Frågor börjar när det finns många parametrar - du måste göra 4, 8, 16 ... konstruktörer.
klass Obj2 { offentliga : Obj ( const std :: string & x1 , const std :: string & x2 ) : field1 ( x1 ), field2 ( x2 ) {} Obj ( const std :: sträng & x1 , std :: sträng && x2 ) : fält1 ( x1 ), fält2 ( std :: flytta ( x2 )) {} // ...och ytterligare två privata överbelastningar : std :: sträng fält1 , fält2 ; }Det finns två sätt att inte multiplicera entiteter, "by-value+move" idiom och metaprogrammering , och för det senare har en andra C++11-mekanism gjorts.
Referenskollaps förklaras bäst med den här koden .
använder One = int && ; använder Two = One & ; // sedan Two = int&När man skickar till godkända referenser får man inte bara reda på vilken typ av parameter som skickas till funktionen, utan även en bedömning om det är ett rvärde eller ett lvärde . Om parametern som skickas till funktionen är ett lvärde , kommer det ersatta värdet också att vara en referens till lvärdet . Med det sagt, det noteras att deklarering av en mallparametertyp som en &&-referens kan ha intressanta bieffekter. Till exempel blir det nödvändigt att explicit specificera initialiserare för alla lokala variabler av en given typ, eftersom när de används med lvalue- parametrar, kommer typinferens efter instansiering av mallen att tilldela dem värdet av en lvalue- referens, som, genom språkkrav, måste ha en initialiserare [6] .
Länklimning tillåter följande mönster:
klass Obj { offentliga : mall < classT > _ Obj ( T && x ) : field ( std :: forward < T > ( x )) {} // hoppa framåt och gör det rätt privat : // nedan förklarar vi varför du inte kan göra det utan en explicit framåtfunktion std :: strängfält ; _ }För sådana tillfälliga referenser har kompilatorer lagt till särskilda regler [7] , på grund av vilka...
Låt oss återgå till mallkonstruktorn Obj::Obj. Om du inte överväger främmande typer, utan bara sträng, är tre alternativ möjliga.
Det tredje alternativet är bra, men enkel typslutning kan inte skilja det första alternativet från det andra. Men i den första varianten behövs std::move för maximal prestation, i den andra är det farligt: tilldelning med ett drag kommer att "tömma" strängen, vilket fortfarande kan vara användbart.
Låt oss gå tillbaka till vår mallkonstruktör.
mall < classT > _ Obj ( T && x ) : fält ( std :: framåt < T > ( x )) {}Mallen används endast i mallar (det finns tillräckligt med kod som inte är mall ). Den kräver att typen är explicit specificerad (annars går den inte att skilja från ), och antingen görs ingenting eller expanderar till . std::forwardstd::moveObj(string&&)Obj(string&)std::move
Det andra sättet att inte multiplicera enheter: parametern tas av värde och skickas vidare genom . std::move
klass Obj { offentliga : Obj ( std :: sträng x ) : fält ( std :: flytta ( x )) {} privat : std :: stringfield ; _ }Används när man flyttar ett objekt är det betydligt "lättare" än att kopiera, vanligtvis i icke-mallkod.