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