Const, Move og RVO

Const, Move og RVO

C++ er et overraskende sprog. Nogle gange er simple ting ikke så enkle i praksis. Sidste gang argumenterede jeg for det i funktionslegemer const skal bruges det meste af tiden. Men to tilfælde blev savnet:ved flytning og ved returnering af en værdi.

Gør const indflydelse flytte og RVO?

Introduktion

Bare for at huske, vi taler her om at bruge const for variable inde i funktionslegemer. Ikke om const for en returtype, const inputparametre eller const metoder. For eksempel:

Z foo(T t, X x)
{
    const Y y = superFunc(t, x);
    const Z z = compute(y);
    return z;
}

I koden ovenfor er det bedst, når y og z erklæres som konstante.

Så hvad er problemet så?

Først og fremmest kan du ikke flytte fra et objekt, der er markeret somconst .

Et andet potentielt problem er, når en compiler forsøger at bruge (navngivet) returværdioptimering (NRVO eller RVO ). Kan det fungere, når variabelen, der skal fjernes, er konstant?

Jeg fik følgende kommentar frau/sumo952:

Lad os prøve at tænke på bedre råd. Men først skal vi forstå, hvad der er problemet med flytning og RVO.

Flyttesemantik

Flyt semantik (se dette fantastiske indlæg for mere:C++ Rvalue ReferencesExplained
Af ThomasBecker) gør det muligt for os at implementere en mere effektiv måde at kopiere store objekter på. Mens værdityper alligevel skal kopieres byte for byte, kan typer som containere, ressourcehåndtag nogle gange kopieres ved stjæling .

For eksempel, når du vil 'flytte' fra en vektor til en anden i stedet for at kopiere alle data, kan du bare udveksle pointere til den hukommelse, der er allokeret på heapen.

Flytteoperationen kan ikke altid aktiveres, den udføres på r-værdi referencer

  • objekter, der normalt er tidsmæssige, og det er sikkert at stjæle fra dem.

Her er noget eksplicit kode til flytning:

a = std::move(b); 
// b is now in a valid, but 'empty' state!

I det simple kodestykke ovenfor, hvis objektet a har en flytteopgaveoperatør (eller en flyttekonstruktør afhængigt af situationen), kan vi stjæle ressourcer fra b .

Når b er markeret som const i stedet for en r-værdi reference, får vi const r-værdi' reference. Denne type kan ikke videregives til flytteoperatører, så en standard kopikonstruktør eller tildelingsoperatør vil blive aktiveret. Ingen præstationsgevinst!

Bemærk, at der er const r-værdier i sproget, men deres brug er ret eksotisk, se dette indlæg for mere info, hvis det er nødvendigt:Hvad er constrvalue references good for?og også i CppCon 2014:Stephan Lavavejtalk.

OK... men er dette virkelig et stort problem for os?

Midlertidige objekter

Først og fremmest virker flyttesemantik det meste af tiden på midlertidige objekter, så du vil ikke engang se dem. Selvom du har nogle konstante objekter, kan resultatet af en funktionskaldelse (som en binær operator) være noget andet, og normalt ikke konst.

const T a = foo();
const T b = bar();
const T c = a + b; // result is a temp object
// return type for the + operator is usually not marked as const
// BTW: such code is also a subject of RVO... read later...

Så i en typisk situation vil objekternes konstanthed ikke påvirke bevægelsessemantik.

Eksplicitte træk

Et andet tilfælde er, når du ønsker at flytte noget eksplicit. Med andre ord tager du din variabel, som er en l-værdi, og du vil lave den, som den var en r-værdi.

Den centrale retningslinje nævner, at vi normalt ikke bør ringe std::move eksplicit:

ES.56:

Og i det tilfælde, hvor du virkelig har brug for en sådan operation, antager jeg, at du ved, hvad du laver! Bruger const her er ikke en god idé. Så jeg er enig i, at mit råd kan ændres lidt i den sammenhæng.

Returnering af en værdi

I det tilfælde, hvor copy elision ikke kan anvendes, vil compileren forsøge at bruge en move assignment operator eller en move constructor, hvis det er muligt. Hvis disse ikke er tilgængelige, skal vi udføre en standardkopi.

For eksempel:

MyType ProduceType(int a)
{
    MyType t;
    t.mVal = a;
    return t;
}

MyType ProduceTypeWithConst(int a)
{
    const MyType t = ProduceType(a);
    return t;
}

MyType t;
t = ProduceTypeWithConst(1);

Hvad er det forventede output her? Der skal helt sikkert oprettes to objekter t og et objekt inde i funktionerne. Men når du vender tilbage fra ProduceTypeWithConst compileren vil forsøge at påkalde move, hvis det er muligt.

MyType()
MyType()
operator=(MyType&& v)
~MyType()
~MyType()

Som du kan se markere returobjektet som const ikke forårsaget nogen problemer med at udføre en flytning. Det ville kun være et problem, når funktionen returnerede en const MyType , men det returnerer MyType så vi er i sikkerhed her.

Så alt i alt ser jeg ikke et kæmpe problem med flyttesemantik.

Lad os nu gå til et andet emne RVO...

Optimering af afkastværdi

RVO er en optimering udført af de fleste compilere (og obligatorisk inC++17!). Når det er muligt, vil compileren ikke oprette en ekstra kopi til det midlertidige returnerede objekt.

MyType ProduceType()
{
    MyType rt;
    // ...
    return rt;
}

MyType t = ProduceType(); // (N)RVO

Det kanoniske C++ ville gøre noget som dette i koden ovenfor:

  • konstruer rt
  • kopi rt til et midlertidigt objekt, der vil blive returneret
  • kopiér det midlertidige objekt til t

Men compileren kan fjerne disse kopier og bare initialisere t én gang.

Du kan læse mere om (N)RVO i artiklerne fra FluentCpp og UndefinedBehaviour.

Returnerende const

Hvad sker der, hvis dit objekt er const ? Ligesom:

MyType ProduceTypeWithConst(int a)
{
    const MyType t = ProduceType(a);
    return t;
}

MyType t = ProduceTypeWithConst(1);

Kan RVO anvendes her? Svaret er Ja.

Det ser ud til, at const gør ingen skade her. Hvad der kan være problemet er, når RVO ikke kan påberåbes, så er det næste valg at bruge flytende semantik. Men det har vi allerede dækket i afsnittet ovenfor.

De lidt ændrede råd

I funktionsorganer:
Brug const når det er muligt. Undtagelser:
* Hvis du antager, at typen er flytbar, når du eksplicit ønsker at flytte en sådan variabel, så tilføjer du const kan blokere bevægelsessemantik.

Alligevel, hvis du er usikker, og du arbejder med nogle større objekter (der har aktiveret flytning), er det bedst at måle måle mål.

Nogle flere retningslinjer:

Grundlæggende retningslinjer, F.20:

Oversigt

Til at begynde med var jeg bekymret over nogle negative virkninger af at brugeconst i tilfælde af flytning og RVO, synes jeg ikke det er så alvorligt. Det meste af tiden kan compileren fjerne kopier og håndtere midlertidige objekter korrekt.

Du kan lege med koden her:@coliru.

  • Gik jeg glip af noget?
  • I hvilke situationer er du bange for at sætte konst.