2.9 KiB
+++ 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í.
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.
// 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:
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:
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 funkcefoo()
- 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:
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;
}