C++のムーブ，コピー，NRVOの具体例による解説

# C++のムーブセマンティクスとNRVO
C++では，コピーによるオーバーヘッドを避けるためにムーブセマンティクスがサポートされています．これを活用する例として，ベクトル演算を題材に解説を試みます．簡単に言えば，右辺値はコンパイラだけが知る計算や処理の一時的な中間結果や作業用変数であり，メモリから近々削除される値です．そのため，そのメモリ領域を横取りすることができ，コピーを省略することができます．左辺値は固定されたアドレスに変数として読み書きされる値です．よって，その値を横取りすることはできません．基本的に左辺値参照は`const &`で表され，右辺値参照は`&&`で表されます．

NRVO (Named Return Value Optimization) は，関数の戻り値を呼び出し側で用いる際に，コピーやムーブ操作を省略する最適化技術です．例えば，関数内でローカル変数を用いて計算を行い，そのローカル変数の値をそのまま戻り値とし，呼び出し側でこの戻り値を変数に代入するような状況では，ローカル変数の代わりにこの変数を直接計算に用いることで，余分なコピーやムーブ操作を省略できます．これにより，パフォーマンスの向上が期待できます．

# ベクトル演算のC++プログラム
`class Vector`は，`std::vector<int>`型のデータを持ち，整数のベクトルを保持します．このクラスは，加算の複合代入演算 `+=` と二項演算の加算 `+` をサポートします．なお，ベクトルは同じ長さでなければなりませんが，プログラムを簡単にするために，同じ長さかどうかのチェックは省略しています．

* `Vector::Vector(std::vector<int> data)`: ベクトルの初期値を指定してベクトル変数を生成するコンストラクタ．
* `Vector::Vector(const Vector &vector)`: コピーコンストラクタ．左辺値参照のベクトル変数`vector`をコピーして，新しいベクトル変数を生成するコンストラクタ．
* `Vector::Vector(Vector &&vector)`：ムーブコンストラクタ．右辺値参照のベクトル変数`vector`をムーブして，新しいベクトル変数を生成するコンストラクタ．
* `Vector::Vector &operator=(const Vector &vector)`:コピー代入．左辺値参照のベクトル変数`vector`の値を代入の左辺のベクトル変数にコピーします．
* `Vector::Vector &operator=(Vector &&vector)`: ムーブ代入．右辺値参照のベクトル変数`vector`の値を代入の左辺のベクトル変数にムーブします．
* `Vector::Vector &operator+=(const Vector &vector)`: 左辺のベクトル変数に右辺の`vector`の値を加算します．

二項演算`+`のためのオーバーロードは，２つの引数が右辺値参照と左辺値参照の４通りの組み合わせについて定義します．
* `Vector &&operator+(Vector &&lhs, Vector &&rhs)`: 右辺値参照`lhs`と右辺値参照`rhs`が引数の場合．`lhs+=rhs`を計算し，`lhs`を`std::move`で右辺値参照として返します．
* `Vector &&operator+(Vector &&lhs, const Vector &rhs)`: 右辺値参照`lhs`と左辺値参照`rhs`が引数の場合．`lhs+=rhs`を計算し，`lhs`を`std::move`で右辺値参照として返します．
* `Vector &&operator+(const Vector &lhs, Vector &&rhs)`:  左辺値参照`lhs`と右辺値参照`rhs`が引数の場合．`rhs+=lhs`を計算し，`rhs`を`std::move`で右辺値参照として返します．
* `Vector operator+(const Vector &lhs, const Vector &rhs)`: 左辺値参照`lhs`と左辺値参照`rhs`が引数の場合．`lhs`で初期化されたベクトル変数`result`を作成し，`result+=rhs`を計算し，計算結果`result`の値を返します．このとき，NRVOが適用され，呼び出し元が必要とする領域上で`result`の計算は行われ，`result`の計算結果がreturnに伴いコピーされるこはありません．

```C++:vector.hpp
#ifndef __VECTOR_HPP__
#define __VECTOR_HPP__
#include <iostream>
#include <string>
#include <vector>

// Vector class
class Vector {

  // The data of the vector
  std::vector<int> data;

  // Friend functions
  friend Vector &&operator+(Vector &&lhs, Vector &&rhs);
  friend Vector &&operator+(Vector &&lhs, const Vector &rhs);
  friend Vector &&operator+(const Vector &lhs, Vector &&rhs);
  friend Vector operator+(const Vector &lhs, const Vector &rhs);

public:
  // Returns a string representation of the vector
  std::string str() const {
    std::string result = "(";
    bool first = true;
    for (const auto &x : data) {
      result += (first ? "" : ",") + std::to_string(x);
      first = false;
    }
    result += ")";
    return result;
  }

  // Prints the vector
  void print(const std::string &prefix = "") const {
    std::cout << prefix;
    bool first = true;
    for (const auto &x : data) {
      std::cout << (first ? "(" : ",") << x;
      first = false;
    }
    std::cout << ")" << std::endl;
  }

  // Constructors with initializer list
  Vector(std::vector<int> data) : data(std::move(data)) {
    std::cout << "Vector::Vector(std::vector<int> = " << str()
              << "): Constructor" << std::endl;
  }

  // Copy constructor
  Vector(const Vector &vector) : data(vector.data) {
    std::cout << "Vector::Vector(const Vector & = " << vector.str()
              << "): Copy Constructor" << std::endl;
  }

  // Move constructor
  Vector(Vector &&vector) noexcept : data(std::move(vector.data)) {
    std::cout << "Vector::Vector(Vector && = " << str()
              << ") : Move Constructor" << std::endl;
  }

  // Destructor
  ~Vector() {
    std::cout << "Vector::~Vector() = " << str() << " : Destructor"
              << std::endl;
  }

  // Copy assignment
  Vector &operator=(const Vector &vector) {
    std::cout << "Vector::operator=(const Vector & = " << vector.str() << " : "
              << "Copy assignment" << std::endl;
    data = vector.data;
    return *this;
  }

  // Move assignment
  Vector &operator=(Vector &&vector) noexcept {
    std::cout << "Vector::operator=(Vector && = " << vector.str() << ") : "
              << "Move assignment" << std::endl;
    data = std::move(vector.data);
    return *this;
  }

  // Operator +=
  Vector &operator+=(const Vector &vector) {
    std::cout << "Vector::operator+=(const Vector &) : " << str()
              << " += " << vector.str() << std::endl;
    for (int i = 0; i < data.size(); ++i)
      data[i] += vector.data[i];
    return *this;
  }
};

// Operator + overloads
Vector &&operator+(Vector &&lhs, Vector &&rhs) {
  std::cout << "operator+(Vector && = " << lhs.str() << ", "
            << "Vector && = " << rhs.str() << ") : Move" << std::endl;
  lhs += rhs;
  return std::move(lhs);
}

Vector &&operator+(Vector &&lhs, const Vector &rhs) {
  std::cout << "operator+(Vector && = " << lhs.str()
            << ", const Vector & = " << rhs.str() << ") : Move" << std::endl;
  lhs += rhs;
  return std::move(lhs);
}

Vector &&operator+(const Vector &lhs, Vector &&rhs) {
  std::cout << "operator+(const Vector & = " << lhs.str()
            << ", Vector && = " << rhs.str() << ") : Move" << std::endl;
  rhs += lhs;
  return std::move(rhs);
}

Vector operator+(const Vector &lhs, const Vector &rhs) {
  std::cout << "operator+(const Vector & = " << lhs.str()
            << ", const Vector & = " << rhs.str() << ") : Copy (NRVO)"
            << std::endl;
  Vector result(lhs);
  result += rhs;
  return result;
}

#endif // __VECTOR_HPP__
```

# 実行例

### その1：コピーコンストラクタ
ベクトル変数`v1`をベクトル`(1, 2, 3)`で初期化し作成します．そして，ベクトル変数`v2`を`v1`のコピーとして作成します．

```C++
#include "vector.hpp"

int main() {
  Vector v1({1, 2, 3});
  v1.print("v1 = ");
  Vector v2(v1);
  v2.print("v2 = ");
}
```
### 実行結果
`v1`はコンストラクタで作成され，`v2`はコピーコンストラクタで作成されています．
```
Vector::Vector(std::vector<int> = (1,2,3)): Constructor
v1 = (1,2,3)
Vector::Vector(const Vector & = (1,2,3)): Copy Constructor
v2 = (1,2,3)
Vector::~Vector() = (1,2,3) : Destructor
Vector::~Vector() = (1,2,3) : Destructor
```

### その2：ムーブコンストラクタ
`v2`の初期値を`v1 + Vector({1, 1, 1}`に変更します．
```C++
int main() {
  Vector v1({1, 2, 3});
  v1.print("v1 = ");
  Vector v2(v1 + Vector({1, 1, 1}));
  v2.print("v2 = ");
  }
```

### 実行結果
1. `Vector({1, 2, 3})`のコンストラクタが呼ばれ`v1`となります．
2. `Vector({1, 1, 1})`のコンストラクタが呼ばれ，右辺値となります．
2. `v1`が左辺値，`(1, 1, 1)`が右辺値なので，ムーブセマンティクスで`operator+`が呼ばれます．
3. 右辺値`(1, 1, 1)`を上書きするように`operator+=`が呼ばれ， `(1,1,1) += (1,2,3)`が行われます．計算結果は，`(1, 1, 1)`を上書きし，`(2,3,4)`となります．
4. `operator+=`の計算結果`(2,3,4)`は右辺値なので，`v2`のためのムーブコンストラクタが呼び出され，それを横取りし，コピー代入を避けます．
```
Vector::Vector(std::vector<int> = (1,2,3)): Constructor
v1 = (1,2,3)
Vector::Vector(std::vector<int> = (1,1,1)): Constructor
operator+(const Vector & = (1,2,3), Vector && = (1,1,1)) : Move
Vector::operator+=(const Vector &) : (1,1,1) += (1,2,3)
Vector::Vector(Vector && = (2,3,4)) : Move Constructor
Vector::~Vector() = () : Destructor
v2 = (2,3,4)
Vector::~Vector() = (2,3,4) : Destructor
Vector::~Vector() = (1,2,3) : Destructor
```

### その3：NRVO (Named Return Value Optimization)
ベクトル変数`v3`を`v1 + v2`で初期化し作成します．
```C++
#include "vector.hpp"

int main() {
  Vector v1({1, 2, 3});
  v1.print("v1 = ");
  Vector v2({1, 1, 1});
  v2.print("v2 = ");
  Vector v3(v1 + v2);
  v3.print("v3 = ");
}
```

### 実行結果1：NRVOが適用される場合
普通にg++でコンパイルするとNRVOが適用されます．
1. `v1 + v2`の計算を行うとき，２つの左辺値の加算なので，`Vector operator+(const Vector &lhs, const Vector &rhs)`が呼ばれます．
2. 左辺値は横取りできないので，ローカル変数として，`lhr`で初期化されたベクトル変数`result`を作成します．この時，コピーコンストラクタが呼ばれます．
3. `result += rhs`が行われ，加算結果が`result`に書き込まれます．
4. `result`の値が`v3`に代入されるはずですが，省略されています．NRVOにより2.と3.の処理が`result`ではなく，代わりに`v3`に対して行われ，コピーやムーブを避けています．
```
Vector::Vector(std::vector<int> = (1,2,3)): Constructor
v1 = (1,2,3)
Vector::Vector(std::vector<int> = (1,1,1)): Constructor
v2 = (1,1,1)
operator+(const Vector & = (1,2,3), const Vector & = (1,1,1)) : Copy (NRVO)
Vector::Vector(const Vector & = (1,2,3)): Copy Constructor
Vector::operator+=(const Vector &) : (1,2,3) += (1,1,1)
v3 = (2,3,4)
Vector::~Vector() = (2,3,4) : Destructor
Vector::~Vector() = (1,1,1) : Destructor
Vector::~Vector() = (1,2,3) : Destructor
```

### 実行結果2：NRVOが適用されない場合
NRVOを適用しないためにg++にオプション`-fno-elide-constructors`をつけてコンパイルし実行すると，下の結果が得られます．違いは，ムーブコンストラクターが動作している点です．`return result`による関数の戻り値を引数するムーブコンストラクターを実行し，ベクトル変数`v3`が作成されています．関数の戻り値は右辺値なので，コピーコンストラクタでなくムーブコンストラクタが呼ばれています．そしてデストラクタが呼ばれ，`result`の領域が解放されています．

```
Vector::Vector(std::vector<int> = (1,2,3)): Constructor
v1 = (1,2,3)
Vector::Vector(std::vector<int> = (1,1,1)): Constructor
v2 = (1,1,1)
operator+(const Vector & = (1,2,3), const Vector & = (1,1,1)) : Copy (NRVO)
Vector::Vector(const Vector & = (1,2,3)): Copy Constructor
Vector::operator+=(const Vector &) : (1,2,3) += (1,1,1)
Vector::Vector(Vector && = (2,3,4)) : Move Constructor
Vector::~Vector() = () : Destructor
v3 = (2,3,4)
Vector::~Vector() = (2,3,4) : Destructor
Vector::~Vector() = (1,1,1) : Destructor
Vector::~Vector() = (1,2,3) : Destructor
```

### その4：もう少し複雑な例
3つのベクトル変数の合計`v1 + v2 + v3`を計算しています．

```
int main() {
  Vector v1({1, 2, 3});
  v1.print("v1 = ");
  Vector v2({1, 1, 1});
  v2.print("v2 = ");
  Vector v3({1, -1, 2});
  v3.print("v3 = ");
  (v1 + v2 + v3).print("v1 + v2 + v3 = ");
}
```

### 実行結果1：NRVOが適用される場合
合計`v1 + v2 + v3`の計算に注目します．まず，`v1 + v2`の計算が行われ，`+ v3`が行われます．
1. `v1 + v2`は，左辺値+左辺値なので，ローカル変数の`result`に計算結果が書き込まれ，それが呼び出し元で作業用変数に代入され右辺値となります．
2. この作業用変数と`v3`の加算のため，`operator+(Vector &&lhs, const Vector &rhs)`が呼び出され，作業用変数に上書きされ計算結果となります．
3. NRVOにより`result`を計算に用いず，代わりに呼び出し元の作業用変数を用います．これにより，コピーやムーブが省略されます．

```
Vector::Vector(std::vector<int> = (1,2,3)): Constructor
v1 = (1,2,3)
Vector::Vector(std::vector<int> = (1,1,1)): Constructor
v2 = (1,1,1)
Vector::Vector(std::vector<int> = (1,-1,2)): Constructor
v3 = (1,-1,2)
operator+(const Vector & = (1,2,3), const Vector & = (1,1,1)) : Copy (NRVO)
Vector::Vector(const Vector & = (1,2,3)): Copy Constructor
Vector::operator+=(const Vector &) : (1,2,3) += (1,1,1)
operator+(Vector && = (2,3,4), const Vector & = (1,-1,2)) : Move
Vector::operator+=(const Vector &) : (2,3,4) += (1,-1,2)
v1 + v2 + v3 = (3,2,6)
Vector::~Vector() = (3,2,6) : Destructor
Vector::~Vector() = (1,-1,2) : Destructor
Vector::~Vector() = (1,1,1) : Destructor
Vector::~Vector() = (1,2,3) : Destructor
```

### 実行結果2：NRVOが適用されない場合
同様に，NRVOを適用しないためにg++にオプション`-fno-elide-constructors`をつけてコンパイルし実行します．`v1 + v2`の計算のあと，ムーブコンストラクタが呼ばれ，戻り値`result`が作業用変数にムーブされています．そして，`result`の領域を解放するためのデストラクタが呼ばれています．

```
Vector::Vector(std::vector<int> = (1,2,3)): Constructor
v1 = (1,2,3)
Vector::Vector(std::vector<int> = (1,1,1)): Constructor
v2 = (1,1,1)
Vector::Vector(std::vector<int> = (1,-1,2)): Constructor
v3 = (1,-1,2)
operator+(const Vector & = (1,2,3), const Vector & = (1,1,1)) : Copy (NRVO)
Vector::Vector(const Vector & = (1,2,3)): Copy Constructor
Vector::operator+=(const Vector &) : (1,2,3) += (1,1,1)
Vector::Vector(Vector && = (2,3,4)) : Move Constructor
Vector::~Vector() = () : Destructor
operator+(Vector && = (2,3,4), const Vector & = (1,-1,2)) : Move
Vector::operator+=(const Vector &) : (2,3,4) += (1,-1,2)
v1 + v2 + v3 = (3,2,6)
Vector::~Vector() = (3,2,6) : Destructor
Vector::~Vector() = (1,-1,2) : Destructor
Vector::~Vector() = (1,1,1) : Destructor
Vector::~Vector() = (1,2,3) : Destructor
```

# まとめ
今回出てきた内容をまとめると，次のパターンとなります．
### NRVOを適用する関数の書き方
ローカル変数`result`に計算処理を行い，それをそのまま返す．`result`はこの関数内だけで用いられる変数なので，呼び出し元に戻るときに削除される．したがって，NRVOが適用されることで，コンパイラはローカル変数`result`を使用せず，呼び出し元の適切な場所を`result`の代わりに用いるため，`result`からのコピーやムーブが省略される．
```C++
Vector f(){
  Vector result;
  // resultへの計算処理
  return result;
}
```
### 引数が右辺値参照の場合
引数で受け取った右辺値参照`vector`を流用して計算処理を行う．この引数`vector`は呼び出し元にとって一時的な計算結果などであり，近々削除される不要なものである．この引数上で計算処理を行い，右辺値参照として返す．返り値は右辺値として扱われるので，呼び出し元で変数に代入する場合，ムーブが行われる．
```C++
Vector &&f(Vector && vector){
  // vectorへの計算処理
  return std::move(vector);
}
```
### 引数が左辺値参照の場合
引数`vector`は左辺値参照のため，関数内で変更できない．一旦ローカル変数`result`にコピーし，計算処理後にこのローカル変数`result`を返り値とする．NRVOが適用されることで，コンパイラはローカル変数`result`を使用せず，呼び出し元の適切な場所を`result`の代わりに用いるため，`result`からのコピーやムーブが省略される．
```C++
Vector f(const Vector &vector){
  Vector result(vector)
  // resultへの計算処理
  return result;
}