Verplaatsen in C++ zonder std::move

Return value optimization (RVO)

Een van de grootste vijanden van prestaties zijn onnodige kopieeroperaties.

Je bent waarschijnlijk bekend met return value optimization (RVO). Dit is waar je, indien mogelijk, altijd naar moet streven. RVO houdt in dat het geretourneerde object niet op de stack binnen de functie wordt gemaakt, maar aan de aanroepzijde, waar de waarde uiteindelijk terechtkomt. Dit bespaart zowel kopieer- als verplaatsacties. De standaard verwijst hiernaar als copy elision, aangezien de standaard nooit spreekt over compiler-optimalisaties.

Sinds C++17 krijg je gegarandeerde copy elision in het volgende geval:

Apple RVO()
{
    return {};
}

Dit is pure RVO. Daarnaast is er named return value optimization (NRVO):

Apple NRVO()
{
    Apple res{};
    return res;
}

Dat laatste is niet onderhevig aan gegarandeerde copy elision. Waarschijnlijk betaal je hier echter ook niet voor een kopieer- of verplaatsactie.

Verplaatsen in plaats van kopiëren

Het volgende beste alternatief na copy elision is het verplaatsen (moving) van een object. Sinds C++11 zijn er in de taal een aantal plaatsen waar impliciete verplaatsingen plaatsvinden:

Apple Fun(Apple val)
{
    return val;
}

In deze code wordt het resulterende object verplaatst vanuit de parameter.

Er zijn echter gevallen in de taal die iets ingewikkelder liggen. Het eerste voorbeeld is het volgende:

Apple Cat(Apple&& val)
{
    return val;
}

Je hebt hier een functie die een rvalue-referentie als parameter neemt en het zojuist ontvangen object retourneert. Hoewel deze code compileert, krijg je vóór C++20 een kopieerconstructie van de retourwaarde (bij een conforme compiler zoals GCC). De minder conforme compiler Clang geeft je een verplaatsconstructie. Soms kan het dus beter om je niet strikt aan het boek te houden.

Het andere voorbeeld, dat ik minder prettig vind, is het volgende:

Apple&& Cat(Apple&& val)
{
    return val;
}

Je hebt hier opnieuw een functie die een rvalue-referentie als parameter neemt, maar ditmaal wordt er een rvalue-referentie geretourneerd. In dit geval zou de code niet compileren zonder de retourwaarde handmatig te verplaatsen, wat ingaat tegen mijn advies.

Om eerlijk te zijn is in beide gevallen het verplaatsen van de retourwaarde vereist om het beste resultaat te behalen. Beide gevallen gaan dus in tegen mijn advies.

Zodra je echter overschakelt naar de C++23-modus, voeren beide gevallen een impliciete verplaatsing uit. Er is geen std::move vereist. Je kunt mijn regel om std::move zelden te gebruiken nu dus nog strikter volgen!