+++ title = "C++ move semantika" description = "Jak funguje move v C++" date = 2021-03-03 draft = false slug = "cpp-move" [taxonomies] categories = ["programování"] tags = ["C++"] +++ Slouží k přesunu věcí z jednoho objektu do druhého. Originální objekt může po přesunu být v nekonzistentním stavu. ## Lhodnota a Rhodnota (lvalue, rvalue) Definice z C je, že lvalue je výraz, který může být na levé i pravé straně přířazení a rvalue je výraz, který může být pouze na pravé straně přířazení. ```cpp int a = 42; int b = 43; // a a b jsou lvalue: a = b; // ok b = a; // ok a = a * b; // ok // a * b je rvalue: int c = a * b; // ok, rvalue na pravé straně přiřazení a * b = 42; // chyba, rvalue na levé straně přiřazení ``` V C++ je to o trošku složitější. Jsou tam malé rozdíly v použití oprátoru `&`, např. v C nelze vrátit z funkce referenci `int& funkce() {}`. Toto lze pouze v C++. Definovat lvalue a rvalue teda můžeme tak, že lvalue je výraz, ze kterého jde udělat reference na paměť pomocí operátoru `&`, rvalue je všechno co není lvalue. ```cpp // lvalues: // int i = 42; i = 43; // ok, i je lvalue int* p = &i; // ok, i je lvalue int& foo(); foo() = 42; // ok, foo() je lvalue int* p1 = &foo(); // ok, foo() je lvalue // rvalues: // int foobar(); int j = 0; j = foobar(); // ok, foobar() je rvalue int* p2 = &foobar(); // chyba, nelze získat adresu paměti rvalue j = 42; // ok, 42 je rvalue ``` ## Move semantika Mějme třídu `X`, která v sobě drží raw ukazetel `m_pointer` a délku dat `m_length`. Pokud instanci této třídy budeme chtít přiřadit operátorem `=`, uděláme to nějak takhle: ```cpp X& X::operator=(X const & rhs) { // uvolnit původní paměť free(this->m_pointer); // kopie paměti this->m_length = rhs.m_length; this->m_pointer = malloc(rhs.m_length); memcpy(this->m_pointer, rhs.m_pointer, rhs.m_length); } ``` Třídu následně použijeme takto: ```cpp X foo(); X x; // nějaké použití x x = foo(); ``` Na posledním řádku se stane: - zkopíruje se paměť `m_pointer` z dočasné instance vrácené z funkce `foo()` - uvolní se původní paměť držená instancí `x` a nahradí se novou z dočasné instance - uvolní se dočasná instance Toto sice funguje, ale efektivnější je v tomto případě pouze přesunout ukazetel z dočasné instance do instance `x`. Funkce `foo()` ale vrací rvalue, takže nelze získat adresu paměti operátorem `&`. Od C++11 je proto zavedený nový operátor `&&`, kterým lze získat referenci na rvalue, tedy místo v paměti, kde se nachází dočasná instance vrácená z `foo()`. Operátor přiřazení pak můžeme definovat jako: ```cpp X& X::operator=(X&& rhs) { this->m_length = rhs.m_length; this->m_pointer = rhs.m_pointer; rhs.m_pointer = nullptr; rhs.m_length = 0; } ```