You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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 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:

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;
}