Waarom voorkomt std::move RVO?

In veel gevallen treedt RVO in werking wanneer een local wordt geretourneerd vanuit een functie. Ik dacht echter dat het expliciet gebruiken van std::moveop zijn minst verplaatsing zou afdwingen wanneer RVO niet plaatsvindt, maar dat RVO wordt waar mogelijk nog steeds toegepast. Het lijkt er echter op dat dit niet het geval is.

#include "iostream"
class HeavyWeight
{
public:
    HeavyWeight()
    {
        std::cout << "ctor" << std::endl;
    }
    HeavyWeight(const HeavyWeight& other)
    {
        std::cout << "copy" << std::endl;
    }
    HeavyWeight(HeavyWeight&& other)
    {
        std::cout << "move" << std::endl;
    }
};
HeavyWeight MakeHeavy()
{
    HeavyWeight heavy;
    return heavy;
}
int main()
{
    auto heavy = MakeHeavy();
    return 0;
}

Ik heb deze code getest met VC++11 en GCC 4.71, debug en release (-O2) config. De copy ctor wordt nooit genoemd. De move ctor wordt alleen aangeroepen door VC++11 in debug config. Eigenlijk lijkt alles goed te gaan met deze compilers in het bijzonder, maar voor zover ik weet is RVO optioneel.

Als ik echter expliciet movegebruik:

HeavyWeight MakeHeavy()
{
    HeavyWeight heavy;
    return std::move(heavy);
}

de move ctor wordt altijd aangeroepen. Dus proberen om het “veilig” te maken, maakt het erger.

Mijn vragen zijn:
– Waarom verhindert std::moveRVO?
– Wanneer is het beter om “op het beste te hopen” en op RVO te vertrouwen, en wanneer moet ik expliciet std::movegebruiken? Of, met andere woorden, hoe kan ik de compiler-optimalisatie zijn werk laten doen en toch beweging afdwingen als RVO niet wordt toegepast?


Antwoord 1, autoriteit 100%

De gevallen waarin kopiëren en verplaatsen is toegestaan, vindt u in sectie 12.8 §31 van de Standaard (versie N3690):

Als aan bepaalde criteria wordt voldaan, mag een implementatie de kopieer-/verplaatsingsconstructie van een klasseobject weglaten, zelfs als de constructor die is geselecteerd voor de kopieer-/verplaatsingsbewerking en/of de destructor voor het object bijwerkingen heeft. In dergelijke gevallen behandelt de implementatie de bron en het doel van de weggelaten kopieer-/verplaatsingsbewerking als gewoon twee verschillende manieren om naar hetzelfde object te verwijzen, en de vernietiging van dat object vindt plaats op een later tijdstip waarop de twee objecten zouden zijn vernietigd zonder de optimalisatie. Deze verwijdering van kopieer-/verplaatsingsbewerkingen, kopieerelisiegenaamd, is toegestaan ​​in de volgende omstandigheden (die kunnen worden gecombineerd om meerdere kopieën te elimineren):

  • in een return-instructie in een functie met een class-retourtype, wanneer de expressie de naam is van een niet-vluchtig automatisch object (anders dan een functie of parameter catch-clause) met hetzelfde cv-unqualified type als het retourtype van de functie, kan de bewerking kopiëren/verplaatsen worden weggelaten door het automatische object rechtstreeks in de retourwaarde van de functie te construeren
  • […]
  • wanneer een tijdelijk klasseobject dat niet is gebonden aan een referentie (12.2) zou worden gekopieerd/verplaatst naar een klasseobject met hetzelfde cv-unqualified type, kan de kopieer-/verplaatsingsbewerking worden weggelaten door het tijdelijke object rechtstreeks te construeren in het doel van de weggelaten kopie/verplaatsing
  • […]

(De twee gevallen die ik heb weggelaten, verwijzen naar het geval van het gooien en vangen van uitzonderingsobjecten die ik minder belangrijk vind voor optimalisatie.)

Dus in een return-statement kan copy-elision alleen voorkomen als de expressie de naam van een lokale variabele is.Als je std::move(var)schrijft, dan is het niet meer de naam van een variabele. Daarom kan de compiler de zet niet uit de weg gaan, als deze zou moeten voldoen aan de standaard.

Stephan T. Lavavej sprak hierover op Going Native 2013en legde precies uw situatie uit en waarom u std::move()hier moet vermijden. Begin met kijken vanaf minuut 38:04. Kortom, wanneer een lokale variabele van het retourtype wordt geretourneerd, wordt deze meestal behandeld als een r-waarde, waardoor verplaatsen standaard mogelijk is.


Antwoord 2, autoriteit 38%

hoe kan ik de compiler-optimalisatie zijn werk laten doen en toch verplaatsing afdwingen als RVO niet wordt toegepast?

Zoals dit:

HeavyWeight MakeHeavy()
{
    HeavyWeight heavy;
    return heavy;
}

Het omzetten van de return in een zet is verplicht.

Other episodes