【C++】初心者のためのクラス設計基礎⑤ ～メモリ管理～

# はじめに

　[前回](https://qiita.com/negi-drums/items/dd83d32d8cc81069bd18)の続きです。超初心者向け記事です。今回はメモリ管理の話をします。短くまとめたいとは思っているのですが、まあいつもの如く長い記事になってしまいました。

# 前回までのあらすじ

　前回までずっと配列クラスを作ってきました。最初の方でいくつかのメンバ関数を導入しました。

```cpp
class Array {
  int length;
  int* array_ptr;
public:
  void allocate(int n) {     // メモリ確保
    length = n;
    array_ptr = new int[length];
  }
  void clear() {             // メモリ解放
    delete[] array_ptr;
    length = 0;
  }
  int size() const { return length; }  // 要素数
};
```

　さらに演算子オーバーロードも導入しました。

```cpp
class Array {
  /* 他のメンバ関数、メンバ変数は省略 */
public:
  int& operator[](int n) {        // アクセス演算子
    assert(n < length);
    return array_ptr[n];
  }
  int operator[](int n) const {   // アクセス演算子 const版
    assert(n < length);
    return array_ptr[n];
  }
  Array& operator=(const Array& arr) {  // コピー代入
    allocate(arr.length);
    for (int i = 0; i < length; i++) {
      array_ptr[i] = arr.array_ptr[i];
    }
    return *this;
  }
  Array& operator=(Array&& arr) {       // ムーブ代入
    length = arr.length;
    array_ptr = arr.array_ptr;  
    return *this;
  }
};
```

　前回はそこにコンストラクタとデストラクタを追加したんでしたね。

```cpp
class Array {
  /* 他のメンバ関数、メンバ変数は省略 */
public:
  // コピーコンストラクタ
  Array(const Array& arr) : length(arr.length), array_ptr(new int[arr.length]) {
    for (int i = 0; i < length; i++) {
      array_ptr[i] = arr.array_ptr[i];
    }
  }

  // ムーブコンストラクタ
  Array(Array&& arr) : length(arr.length), array_ptr(arr.array_ptr) {
  }

  // デフォルトコンストラクタ
  Array() : length{}, array_ptr{nullptr} {
  }

  // その他のコンストラクタ
  Array(int n) : length(n), array_ptr(new int[n]) {
  }

  // デストラクタ
  ~Array() noexcept { delete[] array_ptr; }
};
```

　デストラクタで`delete[]`を行うことで、変数の破棄と同時に自動的に確保した配列の解放ができるようになりました。しかし、二重解放の危険が生じたのでしたね。また他にも上のコードはメモリリークの危険もあります。今回はこれを解決していきましょう。

# クラス設計とメモリ管理

　クラス設計において1位2位を争うレベルで難しいのがメモリ管理です。初心者のうちは結構苦しむ場所でもあります。というのも、どこでメモリリークしていて、どこで二重解放されているかわかりづらいからです。特にデストラクタがどこで呼ばれているか正確に把握するのは、初心者のうちではかなり難しいでしょう。難しいポイントの多いメモリ管理ですが、しっかり整理して考えれば意外と単純だったりします。この章ではメモリ管理の一般的な手法を解説していきます。今回も今までに引き続いて配列クラス`Array`を設計しながら解説します。

## 動機

　前回までに配列クラスのメモリ管理関連のバグをいくつか紹介しているかと思いますが、改めて紹介します。まずはメモリリークの例から紹介します。

```cpp
int main() {
  Array arr(42);
  arr.allocate(10);
  return 0;
}
```

　こんなに短いコードですが、残念ながらメモリリークしています。このままではどこでメモリリークしているかわかりづらいので、呼ばれる関数の中の処理をインライン展開してみると以下のようになります。

```cpp
// 疑似コードです
int main() {
  /* Array arr(42); の中で起きること */
  int arr.length = 42;
  int* arr.array_ptr = new int[42];
  /* ---------- */

  /* arr.allocate(10); の中で起きること */
  arr.length = 10;
  arr.array_ptr = new int[arr.length];
  /* ---------- */

  return 0;

  /* デストラクタ ~Array() の中で起きること */
  delete[] arr.array_ptr;
  /* ---------- */  
}
```

　もうお判りでしょうか。`new[]`が2回行われているのに`delete[]`が1回だけという時点で極めて怪しいですね。最初に確保した42個分の`int`を解放せずに再確保してしまっているので、そこでメモリリークを起こしています。
　続いて、二重解放の例です。

```cpp
int main() {
  Array arr1(42);
  Array arr2 = std::move(arr1);
  return 0;
}
```

　またまた、非常に簡単な例ですが、やはりこれもバグっています。こちらもインライン展開してみましょう。

```cpp
// 疑似コードです
int main() {
  /* Array arr1(42); の中で起きること */
  int arr1.length = 42;
  int* arr1.array_ptr = new int[42];
  /* ---------- */
  /* Array arr2 = std::move(arr1); の中で起きること */
  int arr2.length = arr1.length;
  int* arr2.array_ptr = arr1.array_ptr;
  /* ---------- */

  return 0;
  
  /* デストラクタ arr2.~Array() の中で起きること */
  delete[] arr2.array_ptr;
  /* ---------- */
  /* デストラクタ arr1.~Array() の中で起きること */
  delete[] arr1.array_ptr;
  /* ---------- */
}
```

　今回は先ほどとは逆に`delete[]`が2個あって`new[]`は1個ですね。そして、`arr1.array_ptr`と`arr2.array_ptr`は全く同じメモリ領域を司るので最初に確保した42個分の`int`を2度解放してしまっていることになります。これは二重解放で実行時エラーを引き起こします。
　というわけで、これらを解決していきましょう。

## メモリ管理

　最も簡単なメモリ管理の方針としては以下が挙げられます。

- **新しく確保する前に必ず解放する** (メモリリーク対策)
- **他のオブジェクトと同一のメモリ領域を共有しない** (二重解放対策)
- **メモリを確保していないときはポインタを`nullptr`にしておく** (二重解放対策)

　まあもちろんこの枠に当てはめることができないケースも無数に存在する (特に2つ目のルールが適用できないケースは多い) わけですが、今回はこれで十分でしょう。この3点を念頭に置きつつ設計するだけで非常に楽になります。ちなみに1つ目と2つ目のルールが何故重要かは、1つ前の動機の章で見た例からわかると思いますが、3つ目のルールはいまいちピンとこない方も多いと思います。3つ目のルールにどんな意味があるかというと、実は`delete`や`delete[]`は`nullptr`を渡したときは何もしません。そのため、ポインタを`nullptr`にしておけばどこで`delete`及び`delete[]`が呼ばれても心配ないということです。

```cpp
int main() {
  int* p = nullptr;
  delete p;       // 何もしないので問題ない
  return 0;
}
```

　`new`と`delete`は対にしなければならないというイメージが強い方も多いと思いますが、`nullptr`の時には対になっていなくてもOKです。
　様々なメンバ関数を書く上で考えなければならないパターンは大きく分けて2つです。

- オブジェクトがメモリを保持している時
- オブジェクトがメモリを保持していない時

　まあ当たり前ですね。勿論もっと複雑なケースもたくさんあります。後でやりますが、`operator=`では考えなければならないパターンがもっと多いです。ですが、基本はこの2つを考えておけば十分です。また、先ほどの3つの方針から、**オブジェクトがメモリを保持していない時を考える場合は、ポインタがnullptrであると仮定していいでしょう**。
　さて実際に、`Array`のメンバ関数のメモリ関連のバグを修正していきましょう。constについてはだいぶ前に取り扱いましたが、**constメンバ関数ではクラスの中身が変わらないので、メモリ管理等を考える必要はなさそう**です。そのため、constでないメンバ関数のみを考えましょう。

### Array::allocate(int)

　まずは`Array::allocate(int)`からです。これはメモリを確保する関数でしたね。上で述べた通り、関数呼び出し時にメモリを保持している場合と保持していない場合を考える必要があります。

- メモリを保持している時 : 新しくメモリ確保する前に、保持しているメモリを`delete[]`しなければならない
- メモリを保持していない時 : ポインタは`nullptr`なので、確保前に`delete[]`してもしなくてもいい

　このように考えることができます。結論としては確保前に常に`delete[]`してしまうのが良さそうですね。

```cpp
class Array {
  /* 他のメンバ関数、メンバ変数は略 */
public:
  void allocate(int n) {
    delete[] array_ptr;   // あらかじめ delete[] する
    length = n;
    array_ptr = new int[length];
  }
};
```

　これで、どんな時もメモリリークが起こらなさそうです。

### Array::clear()

　続いて、`Array::clear()`です。メモリ解放の関数でしたね。これは関数呼び出し時だけでなく関数呼び出し後にも注意する必要があります。まずは関数呼び出し時の場合分けから。

- メモリを保持している時 : そのまま解放する
- メモリを保持していない時 : ポインタは`nullptr`なので、解放しても何も起きないのでOK

　まあ単純でしたね。そして、関数呼び出し後ですが、この時オブジェクトは必ずメモリを保持していないので、3つのルールに従うならばポインタは`nullptr`にしなければなりません。よってコードは以下のようにするのが良さそうです。

```cpp
class Array {
  /* 他のメンバ関数、メンバ変数は略 */
public:
  void clear() {
    delete[] array_ptr;
    length = 0;
    array_ptr = nullptr;  // nullptrにする 
  }
};
```

　これによって、`Array::clear()`を呼び出した後も安心して`Array`を使い続けられます。

### Array::Array()

　次はデフォルトコンストラクタです。初期化時に動く関数なので、必ずメモリを保持していない状態から始まりますね。事後条件としては、メモリ確保はないので、ポインタを`nullptr`にすべきでしょう。

```cpp
class Array {
  /* 他のメンバ関数、メンバ変数は略 */
public:
  Array() : length{}, array_ptr{nullptr} {
  }
};
```

　これは変化なしですね。まあ実はわざわざ`array_ptr{nullptr}`と書かなくても`array_ptr{}`だけで`nullptr`に初期化されるのですが。

### Array::Array(int)

　`Array::Array(int)`です。このコンストラクタはメモリ確保がありますね。特にポインタを`nullptr`にする必要などもありません。なので、全くそのままで大丈夫でしょう。

```cpp
class Array {
  /* 他のメンバ関数、メンバ変数は略 */
public:
  Array(int n) : length(n), array_ptr(new int[n]) {
  }
};
```

　強いて変更するとすれば、確保した領域を初期化するか否かでしょう。確保領域は未初期化なので、これを読み取るのはバグの原因になります。なので、あらかじめゼロ初期化しておくと嬉しいケースも多いですが、今回はこのままにしておきます。

### Array::Array(const Array&)

　コピーコンストラクタですね。これはコピー元とコピー先 (`*this`) の両方を考える必要がありますが、コピー元はconstなので特に変化しません。コピー先では、コピー元のオブジェクトがメモリを保持しているかどうかに応じて、メモリを確保する場合としない場合がありますね。メモリを確保しない場合`nullptr`を入れるべきです。

```cpp
class Array {
  /* 他のメンバ関数、メンバ変数は略 */
public:
  Array(const Array& arr) : length(arr.length),
    array_ptr(arr.array_ptr == nullptr ? nullptr : new int[arr.length]) {  // メモリ確保無しならnullptr
    for (int i = 0; i < length; i++) {
      array_ptr[i] = arr.array_ptr[i];
    }
  }
};
```

　こうすれば、メモリ確保しない場合はコピー元は`array_ptr == nullptr`になります。

### Array::Array(Array&&)

　ムーブコンストラクタです。ムーブ元はconstではないので、ムーブ元の状態も関数内で変化させることができます。ムーブコンストラクタでは、ムーブ元の保持するメモリを奪ってしまうことができるんでしたね。3つのルールに従うならば、同じメモリ領域を共有しない方が良いので、ムーブ元のポインタは何か別の値に変更すべきです。ここで何の値を代わりに入れるかですが、やはり3つのルールに従うならば、ムーブ元はメモリ領域を保持しなくなるので、ポインタは`nullptr`にすべきですね。

```cpp
class Array {
  /* 他のメンバ関数、メンバ変数は略 */
public:
  Array(Array&& arr) : length(arr.length), array_ptr(arr.array_ptr) {
    arr.length = 0;
    arr.array_ptr = nullptr;   // nullptrにする
  }
};
```

　これで二重解放を防ぐことができます。ムーブ元がメモリを保持していない場合でもムーブ先とムーブ元の両方のポインタが`nullptr`になるので正しく動きます。
　なお、`arr.length = 0`は念のためです。できるだけ、`length`と実際に確保されているメモリの要素数は一致していた方がバグにならないので、ここでも一致させています。

```cpp
int main() {
  Array arr1(42);
  Array arr2 = std::move(arr1);  // ムーブ
  for (int i = 0; i < arr1.size(); i++) {  // arr1.size() == 0 となり、
    arr1[i] = 0;                           // ムーブ後に使用してもバグにならない。
  }
  return 0;
}
```

　ですが、一般的にはムーブされた変数は再初期化するまで使用しないのが"常識"なので、`arr.length == 0`にしなくてもいいのでは、とも思います。勿論この"常識"は人によって違うと思いますが、僕の場合はムーブされた後の変数は再初期化しない限り使いません。ただ、0を代入するだけなので大したコストでもないということもあり、`arr.length == 0`にするパターンの方が多いように感じます。

### operator=(const Array&)

　コピー代入ですね。コピー元がメモリを持っているときは`delete[]`する必要がありますね。そうでないときは`delete[]`してもしなくてもいいですが、この場合は`delete[]`してしまいましょう。
　注意しなければならないのが自己代入です。つまり、コピー先とコピー元が同じオブジェクトであるときです。もし自己代入した場合、最初に`delete[]`をするとコピー元もコピー先も情報が消えてしまいます。これは困りますね。そのため、この時は何もせずに`return`しましょう。

```cpp
class Array {
  /* 他のメンバ関数、メンバ変数は略 */
public:
  Array& operator=(const Array& arr) {
    if (this == &arr) { return *this; }  // 自己代入でバグらないようにする
    delete[] array_ptr;                  // delete[] しておく
    length = arr.length;
    array_ptr = arr.array_ptr == nullptr ? nullptr : new int[length];  // メモリ確保無しならnullptr
    for (int i = 0; i < length; i++) {
      array_ptr[i] = arr.array_ptr[i];
    }
    return *this;
  }
};
```

　自己代入なんてそんなこと普通しないでしょと思うかもしれませんが、結構やりがちです。以下の例を見てください。

```cpp
#include <random>
#include <ctime>
#include <utility>

int main() {
  const int n = 42;
  Array arr[n] = {};
  std::srand(std::clock());   // 乱数初期化
  int x = std::rand() % n;    // 0 ~ n-1 の中からランダムに選ぶ
  int y = std::rand() % n;    // 0 ~ n-1 の中からランダムに選ぶ
  std::swap(arr[x], arr[y]);  // 入れ替え
  return 0;
}
```

　これはランダムに取り出した2つの`Array`を入れ替えるだけのプログラムですが、もしも`x == y`だった場合は`std::swap`関数内で自己代入 (この場合はコピー代入ではなくムーブ代入ですが) が起きます。これは一見自己代入しているようには見えませんね。このように自己代入の可能性を秘めたコードは結構たくさんあります。無論、自己代入の可能性なんぞ全て見破れるしそれを避けるのがプログラマとしての"常識"だ、というのであれば、自己代入は考慮しなくてもいいでしょうし、考慮しない方がほんの僅かに高速です。ですが、大した高速化にもならないので通常は自己代入の対策をします。

### operator=(Array&& arr)

　さて、ムーブ代入です。これまでのことを総合して考えれば、自己代入をチェックして、ムーブ先を`delete[]`して、ムーブ元のメモリをムーブ先に渡して、ムーブ元のポインタを`nullptr`にすれば良さそうですね。

```cpp
class Array {
  /* 他のメンバ関数、メンバ変数は略 */
public:
  Array& operator=(Array&& arr) {
    if (this == &arr) { return *this; }  // 自己代入でバグらないようにする
    delete[] array_ptr;         // delete[] しておく
    length = arr.length;
    array_ptr = arr.array_ptr;
    arr.length = 0;
    arr.array_ptr = nullptr;    // nullptrにする
    return *this;
  }
};
```

　ムーブ元がもともとメモリを保持していなくても問題なく動作します。例の如く`arr.length = 0`をするかどうかは皆さんの主義信条によります。

### Array::~Array()

　最後はデストラクタです。オブジェクトがメモリを保持しているときもしていない時も`delete[]`してしまっていいでしょう。また、関数終了後はオブジェクトは破棄されるので、ポインタを`nullptr`にする必要はありませんね。

```cpp
class Array {
  /* 他のメンバ関数、メンバ変数は略 */
public:
  ~Array() noexcept { delete[] array_ptr; }
};
```

　すなわちこれはそのままで大丈夫ですね。

### 問題解決の確認

　さてこれで大体の問題は解決されたはずです。動機の章で取り上げた例で確認してみましょう。

```cpp
int main() {
  Array arr(42);
  arr.allocate(10);
  return 0;
}
```

　この例では修正前はメモリリークを起こすのでしたね。修正後の挙動は以下のようになります。

```cpp
// 疑似コードです
int main() {
  /* Array arr(42); の中で起きること */
  int arr.length = 42;
  int* arr.array_ptr = new int[42];
  /* ---------- */

  /* arr.allocate(10); の中で起きること */
  delete[] arr.array_ptr;
  arr.length = 10;
  arr.array_ptr = new int[arr.length];
  /* ---------- */

  return 0;

  /* デストラクタ ~Array() の中で起きること */
  delete[] arr.array_ptr;
  /* ---------- */  
}
```

　`new[]`と`delete[]`がそれぞれ2回ずつ出てきて、ちゃんと対になっていますね。これでメモリリークは回避できます。
　続いて、二重解放の方の例はどうでしょう。

```cpp
int main() {
  Array arr1(42);
  Array arr2 = std::move(arr1);
  return 0;
}
```

　このコードは修正前は二重解放を起こしていましたが、修正後は以下のように動きます。

```cpp
// 疑似コードです
int main() {
  /* Array arr1(42); の中で起きること */
  int arr1.length = 42;
  int* arr1.array_ptr = new int[42];
  /* ---------- */
  /* Array arr2 = std::move(arr1); の中で起きること */
  int arr2.length = arr1.length;
  int* arr2.array_ptr = arr1.array_ptr;
  arr1.length = 0;
  arr1.array_ptr = nullptr;
  /* ---------- */

  return 0;
  
  /* デストラクタ arr2.~Array() の中で起きること */
  delete[] arr2.array_ptr;
  /* ---------- */
  /* デストラクタ arr1.~Array() の中で起きること */
  delete[] arr1.array_ptr;  // nullptr なので OK
  /* ---------- */
}
```

　一見`new[]`1つに対し`delete[]`が2つあるのでバグっているように見えますが、これは大丈夫です。なぜなら、`delete[] arr1.array_ptr`において`arr1.array_ptr == nullptr`だからです。そのため、これは二重解放を回避できています。めでたしめでたし。

## 残った問題 (解決は次回以降)

　実はまだメモリ管理は完全とは言えません。というのも、`new`式が例外を出す場合を考えられていないからです。`new`式が例外を出す状況というのは、例えばメモリ不足等の理由で`operator new`でメモリ確保に失敗した場合や、`new`式内で呼ばれるコンストラクタが例外を出した時等です。

```cpp
struct S {
  S() { throw 1; }   // 例外を出すコンストラクタ
};
int main() {
  S* sp = new S();   // new式内のコンストラクタで例外が出る
  return 0;
}
```

　例外を出した時でもクラスが壊れず正しく動くことを例外安全と呼びます。この例外安全に注意しながらクラス設計をするのが重要なのですが、これはまた次回以降にしましょう。
　あとそろそろテンプレートの話もしたいですね。こちらも次回以降にしましょう。

# メモリ管理再考

　メモリ管理はいろいろなコードフローを考えなければいけないので結構面倒ですね。そこでメモリ管理をサポートするスマートポインタというものが標準ライブラリに提供されています。具体的には`std::unique_ptr`、`std::shared_ptr`、`std::weak_ptr`の3つです。本当は`std::auto_ptr`というものもありましたが、これは`std::unique_ptr`の劣化版で、かなり使いづらいのでC++11以降非推奨、C++17で削除されました。では、3つのスマートポインタの使い方を紹介していきましょう。

## std::unique_ptr

　このスマートポインタは、まさにメモリ管理の章で述べた3つのルールを体現したようなポインタです。新しくメモリ領域を確保する際には、今まで持っていたメモリ領域を自動で解放してくれますし、コピーコンストラクト及びコピー代入不可なのでうっかり他のオブジェクトと同じメモリ領域を共有することは (通常は) ありません。また、メモリ領域を持っていないときは常に`nullptr`になっています。素晴らしいですね。具体的にコード例を見てみましょう。

```cpp
#include <memory>

struct S {  // 適当な構造体
  int x, y;
  int func() const { return x + y; }
  S() = default;
  S(int x_, int y_) : x(x_), y(y_) {}
};

int func(S* ptr) {  // 適当な関数
  return ptr->func();
}

int main() {
  std::unique_ptr<S> ptr1{new S(0, 1)};                 // メモリ確保
  std::unique_ptr<S> ptr2{std::make_unique<S>(0, 1)};   // これも上とほぼ同じ意味
                                                        // 一般的にはnewよりstd::make_uniqueの方が良い
  // 通常のポインタと同様に使える
  ptr2->x;        
  (*ptr2).func(); 

  // 引数を渡す時等、生ポインタが必要なら get() を使う
  func(ptr2.get());
  
  // ptr1 = ptr2;            // error: コピー不可
  ptr1 = std::move(ptr2);    // OK: ムーブはできる。ptr1のメモリは解放され、ptr2のメモリを譲り受ける
                             // ptr2は何も持っていない状態 (nullptr) になる
  return 0;
  // デストラクタで保持しているメモリ領域がdeleteされる
}
```

　こんな感じで、コピー不可な点以外はほとんど通常のポインタと同じように扱えます。メモリを確保する際は、`new`式も使えますが、`std::make_unique`を使う方が多いです。一番大きな理由は単に`new`と書くと`delete`を書かなければならないという発作を抑えるためであって、要は気持ちの問題です。あと、`=`で初期化できるか否かというのもあります。

```cpp
// std::unique_ptr<S> = new S;               // error
std::unique_ptr<S> = std::make_unique<S>();  // OK
```

　これはまた別の機会にちゃんと解説しますが、`explicit`指定子がコンストラクタについているため、`=`での初期化が禁止されています。
　他にも`std::make_unique`を使う利点として、例外安全であるという話もありますが、それもまた別の機会に話すことにします。
　ちなみにコピー不可ではありますが、新しいメモリ領域を確保してポインタの中身をコピーするというのは簡単にできます。

```cpp
ptr1 = std::unique_ptr<S>{new S(*ptr2)};     // Sのコピーコンストラクタが動く
ptr1 = std::make_unique<S>(*ptr2);           // これもほぼ同じ意味
```

　これら2つは`std::unique_ptr`自体をコピーしているわけではなく、中身の`S`をコピーしているだけなのでOKです。
　あと、もう一つ配列版の`std::unique_ptr`も紹介した方が良いでしょう。基本的な使い方はほぼ同じですが、`new`や`delete`の代わりに`new[]`や`delete[]`を使うバージョンです。

```cpp
int main() {
  std::unique_ptr<int[]> ptr1{new int[42]};                 // OK
  std::unique_ptr<int[]> ptr2{std::make_unique<int[]>(42)}; // 上と同じ意味

  ptr2[3];  // 配列アクセス可能

  return 0;
  // デストラクタで保持しているメモリ領域がdelete[]される。
}
```

　通常版と違って配列版の`std::unique_ptr`は`operator[]`のオーバーロードがあります。逆に`operator->`と`operator*`はなくなります。
　気になるのは`std::unique_ptr`の実行速度ですが、最適化すれば生ポインタとほぼ同等の速度になると言われています。なので、ガンガン使っていきましょう。

## std::shared_ptr

　続いて`std::shared_ptr`です。メモリ管理の章で述べた通り、3つのルールのうちの2つ目、他のオブジェクトと同一のメモリ領域を共有しない、というのは理想的ではありますが、実際には複数のオブジェクト間でメモリを共有したくなる状況は結構多いです。そこで使えるのが`std::shared_ptr`です。これは参照カウンタというものを持っていて、いくつのオブジェクトがメモリを共有しているのか数えていてくれます。そして、参照カウンタが0になったタイミングで、保持していたメモリを解放します。そのため、`std::shared_ptr`は`std::unique_ptr`と違ってコピー代入及びコピーコンストラクト可能です。

```cpp
int main() {
  std::shared_ptr<S> ptr1{new S(0, 1)};               // メモリ確保
  std::shared_ptr<S> ptr2{std::make_shared<S>(0, 1)}; // 上とほぼ同じ意味

  // 通常のポインタと同様に使える
  ptr2->x;        
  (*ptr2).func(); 

  // コピー可能
  ptr1 = ptr2;           // ptr1がもともと持っていたメモリ領域は解放され、
                         // ptr2の持っているメモリ領域を共有
  return 0;
  // ptr2とptr1のデストラクタが呼ばれ、メモリ領域が正しくdeleteされる
}
```

　C++17以降配列版の`std::shared_ptr`も提供されるようになりました。また、配列版`std::make_shared`はC++20で実装されました。

```cpp
int main() {
  std::shared_ptr<int[]> ptr1{new int[42]};
  std::shared_ptr<int[]> ptr2{std::make_shared<int[]>(42)};
  ptr2[3];    // 配列アクセス可能
  return 0;
  // デストラクタで正しくdelete[]される
}
```

　`std::unique_ptr`と同様に配列版には`operator[]`のオーバーロードがある代わりに、`operator->`と`operator*`のオーバーロードがなくなります。
　基本的には`std::shared_ptr`より`std::unique_ptr`を使った方が高速ですが、必要に応じて`std::shared_ptr`も使いましょう。ただし、`std::shared_ptr`には気を付けなければいけない点があります。それは循環参照です。つまり、ポインタを辺とみなす有向グラフが巡回グラフになるケースです。

```cpp
struct S {
  std::shared_ptr<S> ptr;
};

int main() {
  std::shared_ptr<S> sptr = std::make_shared<S>();   // ここで新しく確保したSを(A)とする
  sptr->ptr = std::make_shared<S>();                 // ここで新しく確保したSを(B)とする
  sptr->ptr->ptr = sptr;                             // (B)のポインタに(A)のアドレスをコピー
  // sptr -> (A) <-> (B) のような形になっている。
  // つまり、(A)と(B)で循環している
  return 0;
  // sptrが破棄されても(A)も(B)も互いに参照しあっているので、両方とも参照カウンタは 1
  // つまり解放されない。メモリリーク
}
```

　循環参照するとメモリリークを起こします。これを解決するために、`std::weak_ptr`があります。

## std::weak_ptr

　`std::weak_ptr`は主に`std::shared_ptr`の循環参照を解決する目的で使われます。それ以外の目的で使っているのはあまり見たことがありません。`std::weak_ptr`はメモリ管理を一切行わず、`std::shared_ptr`にメモリ管理の全権を委ねているので、参照切れを起こす場合があり、注意が必要です。
　基本的に`std::weak_ptr`を使う際は`lock()`してから使います。実際には`std::weak_ptr`から直接メモリにアクセスするのではなく、`lock()`の戻り値の`std::shared_ptr`を使ってメモリにアクセスします。

```cpp
#include <memory>
int main() {
  std::shared_ptr<int> sptr = std::make_shared<int>();
  std::weak_ptr<int> wptr = sptr;
  {
    std::shared_ptr<int> ptr = wptr.lock();   // lock() は std::shared_ptr を返す
    if (ptr) {             // ptr != nullptr か確認 (参照切れしていないかどうか)
      int x = *ptr;        // ptrが生存している間は安全に使える
    }
  }                        // ptrの生存期間が終わると、安全に使える期間は終了する
  return 0;
}
```

　さて、これで本当に循環参照を解決できているか確認しましょう。

```cpp
#include <memory>
struct W;
struct S {
  std::shared_ptr<W> ptr;
};
struct W {
  std::weak_ptr<S> ptr;
};

int main() {
  std::shared_ptr<S> sptr = std::make_shared<S>();   // ここで新しく確保したSを(A)とする
  sptr->ptr = std::make_shared<W>();                 // ここで新しく確保したWを(B)とする
  sptr->ptr->ptr = sptr;                             // (B)のポインタに(A)のアドレスをコピー
  // sptr -> (A) <-> (B) という形になっている
  // std::shared_ptr だけに注目すると sptr -> (A) -> (B) という形
  // std::weak_ptr だけに注目すると sptr    (A) <- (B) という形
  return 0;
  // sptrが破棄されると(A)は参照カウンタが0になり (std::weak_ptrはカウントされない) 解放される
  // (A)が破棄されると(B)は参照カウンタが0になり解放される
}
```

　解決できましたね。めでたしめでたし。

# 終わりに

　今回はメモリ管理の話でした。スマートポインタは便利ですが、一度は生ポインタでのメモリ管理をやっておいて損はないと思います。次回は`explicit`の話かテンプレートの話のどちらかです。お楽しみに。

おしまい