C++ ムーブセマンティクスと右辺値の概念を初心者向けに

備忘録です。

ムーブ、ムーブセマンティクスの概念、ムーブコンストラクタの話を図を交えて解説します。

本記事は、「ムーブという概念は聞いたことがあるんだけど実際どういうものかわからない」という人が対象(のつもり)です。

## 左辺値と右辺値
ムーブ、ムーブセマンティクスの話をする前に非常に重要な左辺値と右辺値の話をします。

C++では左辺値と右辺値が明確に区別されます。

```c++:左辺値と右辺値
int i = 1;
```
例えばこのコードでは、`int`型で宣言されている`i`が左辺値で、`1`が右辺値になります。

もう少し詳しく説明すると、**左辺値**は`int i`のような名前付きのオブジェクトのことで、**右辺値**はすぐに破棄されてしまうような**一時的なオブジェクト**のことです。
下に右辺値の例を挙げます。

```c++:右辺値の例

1; // リテラル
f(); // 参照がない返り値

Object o;
std::move(o) // std::moveを適応させたlvalue
```
`std::move`という見慣れない命令が出てきましたがとりあえず置いておいて...

右辺値は基本的に評価された時点で破棄されてしまいます。
そのため右辺値を生かしておくには右辺値への参照を持つ**右辺値参照型**という型で束縛する必要があります。

```c++:右辺値参照型
int&& r = 1; // 右辺値1を束縛できた！
```
右辺値参照型は`T&&`で宣言できます。

一方で`Object o`のような名前付きのオブジェクトのことを**左辺値(lvalue)**と呼びます。
左辺値は関数のスコープを外れるまで破棄されません。

ちなみに右辺値参照型は**右辺値への参照**を持つための型のため、左辺値です。
このため、すぐ破棄されてしまう右辺値を生かすことが可能になります。

<img width="728" alt="スクリーンショット 2019-05-02 0.14.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339631/d86ef0d4-f5ff-862c-3a53-87d745fe5275.png">


## 左辺値を右辺値にキャストする std::move
では、さきほど登場した`std::move`についてです。

`std::move`は左辺値を右辺値に**キャスト**します。

```c++
#include <utility>

Object o;
std::move(o) // Object o を Object&& oにキャスト!
```
これで`Object o`は右辺値へキャストされました。

## 左辺値を右辺値にして何が嬉しいの？
`std::move`は左辺値を右辺値にキャストしますがこれは何が嬉しいのでしょうか？
関数のスコープから外れない限り内容が破棄されないことが保証されている左辺値を、一時オブジェクトと同様の右辺値参照に変更する意味はあるのでしょうか？

では、次のような例を考えてみましょう。

```c++:巨大なデータを持つstringを別のstringにコピーする
#include <string>

std::string str("...") // 大量の文字列データを持っているstring型変数

/* strに対して色々な処理を行う */

std::string str_after = str; // 色々な処理を施したstrをstr_afterにコピー！

/* これ以降strに対して処理は行わない */
```
このプログラムでは大量の文字列データを持った`str`に色々な処理を施した後に `str_after` という変数にその内容をコピーしています。
つまり `str_after`は`str`のデータをコピーするために`str`のデータ分のメモリを確保する必要があります。

<img width="517" alt="スクリーンショット 2019-05-01 20.59.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339631/ab486de3-9e2f-854c-df17-ed305658f0b8.png">

動的にメモリを確保するにはOSに必要な分だけのメモリ領域をリクエストする必要があり、C++の処理の中でも時間がかかってしまう部分です。
もしかしたら巨大なデータ領域を新たにリクエストすると、メモリの容量が足りずnullptrを返されるかもしれません。(自分は遭遇したことはありませんが)

プログラム中の`str`はコピー後使われないようです。
もう使わないデータならば、下図のようにデータを移動できればいいのに...

<img width="437" alt="スクリーンショット 2019-05-01 22.14.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339631/7e8af904-36b7-4584-b3b9-d5ad052b4c49.png">

このとき活躍するのがC++11から新たにクラスに定義できるようになった**ムーブコンストラクタ**と**ムーブ代入演算子**です。

ムーブコンストラクタとムーブ代入演算子は一般的に以下のように宣言されます。

```c++:ムーブコンストラクタ、ムーブ代入演算子
// ムーブコンストラクタ
Test (Test &&right) {}
// ムーブ代入演算子
Test &operator=(Test &&right) {}
```
ムーブコンストラクタとムーブ代入演算子は引数に右辺値への参照を取ります。
右辺値ということはこの処理が終わり次第、破棄されてしまう可能性が高いオブジェクトです。

引数にそのようなオブジェクトがあるのですからそのままコピーするのはもったいないです。
どうせ捨てられるならその値の所有権を**奪ってもいいはず**です。

```c++:char[1000]を持つクラス
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;

class Test {
private:
  char *p;
public:
  Test(): p(new char[1000]) {};
  ~Test() {
    delete [] p;
  }

  Test(Test const &right) {
    *this = right;
  }

  Test& operator=(Test const &right) {
    if (this != &right) {
      p = new char[1000]; // 新しいメモリ領域を用意
      memcpy(p, right.p, 1000); // コピー
    }

    return *this;
  }

  Test (Test &&right) {
    *this = std::move(right);
  }

  Test& operator=(Test &&right) {
    printf("move assigned!\n");
    if (this != &right) {
      delete [] p; // 自分のメモリ領域を消して...
      p = right.p; // 相手のメモリ領域を譲り受ける
      right.p = nullptr; // 相手のポインタはnullptrに！
    }
    
    return *this;
  }
};

int main() {
  Test lval1;
  Test lval2;
  Test rval(lval1); // copy constructor
  rval = std::move(lval2); // move代入
}
```

上に示したのが、ムーブコンストラクタとムーブ演算代入を実装したものです。
ムーブでは相手のポインタを自分のポインタに代入し、さらに相手のポインタを`nullptr`で初期化しています。

これにより新たにメモリ領域を作らず所有権を奪うことができました。

<img width="712" alt="スクリーンショット 2019-05-02 2.12.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339631/78bdb27f-1c10-3b6c-5e22-fe40b7bbdad3.png">


つまり、左辺値に`std::move`を適応させることは適応元の左辺値を**ムーブ元にしても良い**と宣言することと同意です。

この処理以降つかわない！みたいなオブジェクトに使用しましょう。

ただし、ムーブコンストラクタは常に生成されるわけではないことに注意です。
クラスがムーブに対応しているかをチェックすることは非常に大事です。

また、現在のSTLのコンテナは基本的にムーブに対応していますが、ムーブコンストラクタに`noexcept`指定がないとコピーコンストラクタが呼ばれてしまうものもある(vectorとか)などの罠もあるため注意が必要です。

## ムーブセマンティクス
**ムーブセマンティクス**とは、左辺値と右辺値が明確に区別されたことによって、ムーブコンストラクタとムーブ代入など値の受け渡し方法に幅ができたこと（と僕は解釈しています）。

## まとめ
* C++において左辺値と右辺値は明確に区別される。
* 右辺値参照は左辺値
* `std::move`は左辺値を右辺値にキャストする。
* ムーブコンストラクタとムーブ代入演算子は右辺値参照を引数にとる。

## 参考文献
[Effective Modern C++](https://www.amazon.co.jp/Effective-Modern-%E2%80%95C-11-14%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%82%92%E9%80%B2%E5%8C%96%E3%81%95%E3%81%9B%E3%82%8B42%E9%A0%85%E7%9B%AE/dp/4873117364)
[C++のムーブと完全転送を知る](http://proc-cpuinfo.fixstars.com/2016/03/c-html/)
[cpprefjp](https://cpprefjp.github.io/)

std::forwardと完全転送の仕様をまとめようと思っていたらmoveについて書いていた...

[^1]: 実際のところ`rvalue`は`prvalue`と`xvalue`の総称で、左辺値にももう少し粒度の細かい分類があるのですが、詳しい解説は [江添さんの解説](https://github.com/EzoeRyou/cpp-intro/blob/master/037-rvalue-reference.md)や [一時オブジェクトの寿命と右辺値参照、ムーブセマンティクスのお話](https://qiita.com/rinse_/items/ad0cc7e351e836595c94)をどうぞ