Direktpass (C++)

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

Bakgrund

Speciellt beteende för parametrar - tillfälliga länkar

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.

Limma länkar

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

  • om T=sträng, kommerObj(string&&)
  • om T=sträng&, kommerObj(string&)
  • om T=konst sträng&, kommer att varaObj(const string&)

Konsekvens: det är inte möjligt att automatiskt veta om en länk är tillfällig

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.

  • T=sträng, instansierad vid , inuti x=sträng&.Obj(string&&)
  • T=sträng&, instansierad vid , inuti x=sträng&.Obj(string&)
  • T=konst sträng&, instansierad vid , inuti x=konst sträng&.Obj(const string&)

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ösning: std::forward

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

Idiom "by-value + 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.

Anteckningar

  1. 1 2 Vandewoerd, 2018 , 6.1 Live, sid. 125.
  2. Horton, 2014 , Perfect Forwarding, sid. 373.
  3. std::forward Arkiverad 19 januari 2019 på Wayback Machine C++-referensen
  4. Vandewoerd 2018 , 15.6.3 Live, sid. 333.
  5. Vandewoerd 2018 , 15.6.3 Live, sid. 332.
  6. Vandewoerd, 2018 , 15.6.2 Överförbara länkar, sid. 331.
  7. Vandewoerd, 2018 , 6.1 Live, sid. 127.

Källor

  • D. Vandevoerd, N. Josattis, D. Gregor. C++ mallar. Utvecklarens referens = C++-mallar. Den kompletta guiden. - 2:a. - St Petersburg.  : "Alfabok", 2018. - 848 sid. - 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 .

Länkar