Move semantics a rvalue

Motivace pro move semantics: chceme zabránit zbytečnému kopírování dat, když je stačí pouze přesunout (jako v následujícím příkladu). Tím můžeme z operace lineární vůči délce dat učinit operaci běžící v konstantním čase. V tomto případě využijeme rvalue reference.

void swapstrings(string s1, string s2) {
    string temp=s1;  // kopírování
    s1=s2;           // kopírování
    s2=temp;         // kopírování
};

Co je rvalue?

Všechny výrazy v C++ jsou buď rvalue nebo lvalue. Zjednodušeně lze říci, že lvalue je každý objekt, který má jméno a existuje tedy i mimo jediný výraz. Rvalue je naopak pouze dočasný objekt, na který existuje vždy jen jeden odkaz.

Rvalue reference

type-id && cast-expression

Rvalue reference nám umožňuje rozlišit lvalue a rvalue, takže můžeme „ukrást“ data nějakého dočasného objektu, na nějž máme rvalue referenci – například pole prvků nějakého kontejneru. Tím se vyhneme zbytečnému kopírování.

Příklad: konkatenace řetězce

int main() {
    string s = string("h") + "e" + "ll" + "o";
    cout << s << endl;
};

V následujícím kódu by bez move semantics v string::operator+ bylo nutno vytvořit nový string, překopírovat do něj data z původního stringu a přidat, co má být přidáno. Nevíme totiž, zda se na původní string neodkazujeme někde jinde v programu. Pokud ale implementujeme move semantics, bude string předán jako rvalue reference a my jej můžeme bezstarostně modifikovat.

Příklad: přidávání prvků do vektoru

Mějme vektor, do kterého přidáváme prvky. Pokud je přesažena kapacita vektoru, musí tento realokovat pro prvky paměť a pak je všechny překopírovat na nové umístění, abychom měli prostor i pro vkládaný prvek. Při kopírování prvku vzniká nejdříve nový prvek, pak se zavolá jeho copy constructor na zkopírování dat z původního prvku, a nakonec je původní prvek odstraněn. Tomuto vpodstatě zbytečnému kopírování se dá zabránit právě pomocí move semantics.

Vlastnosti rvalue reference

  • Lze přetížit funkci, aby brala lvalue a rvalue reference. Smysl má brát jako parametr const lvalue referenci a rvalue referenci – tím jednoznačně rozlišíme, kdy dostáváme modifikovatelný a kdy nemodifikovatelný objekt.
  • Kompilátor bere pojmenovanou rvalue referenci jako lvalue (tedy např. v těle funkce, která přijímá rvalue referenci) – to protože na ni lze po pojmenování odkazovat na více místech programu.
  • Je možné přetypovat lvalue referenci na rvalue. Ideální je použití std::move(T&)

Move semantics

Potřebujeme implementovat především move constructor. Tento bude přijímat rvalue referenci na jinou instanci též třídy. V konstruktoru přiřadíme prvky zdrojového objektu do prvků konstruovaného objektu a nastavíme prvky ve zdrojovém objektu na výchozí hodnoty, abychom zabránili vícenásobnému uvolnění prostředků (např. skrze jeho destruktor).

operator= implementujeme analogicky.

Zdroje

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *


This site uses Akismet to reduce spam. Learn how your comment data is processed.