複数のオブジェクトをどう管理するか 【前編】

# 最初に
　前置として、この記事はC++初心者を対象にしています。中上級者が見ても物足りない可能性があるので注意して下さい。ただ初心者と言っても、C++プログラムの初心者ではなく、どちらかというとクラス設計の話になるので、そういう意味では中級者対象になるかもしれません。C++プログラム自体の初心者は他の記事を見て頑張って勉強して下さい[^3]。
　さて、前置きは置いておきまして、この記事はタイトル通りC++における「複数オブジェクト管理」をテーマとしています。このテーマは初心者なら誰でも躓く点ではないのでしょうか？[^0] そんな人達のためにこの記事を書くことにしました。と言ってもこの記事だけが正しいわけではないので、参考程度に留めておいて下さい。

[後半](https://qiita.com/HnniTns/items/256eb4df19dbdbc8bce3)を上げたので、気になる方は見て下さい。

[^0]: 少なくとも自分は躓きました。

# オブジェクトをどう管理するか
　早速この記事の本題です。例をあげた方が分かりやすいと思うので、例でやります。
　
## 管理の必要性
「クリ・ノコノの2種類の敵を用意してくれ。ただ、動きは別々で。」とプランナーから注文を受けたとします。皆さんはどうしますか？ 「別々のオブジェクトとして、別々に管理」しますか？それとも他の方法で管理しますか？
　「別々のオブジェクトとして、別々に管理」する場合は敵が2種類**だけ**なら問題ないでしょう。

![ダメな例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540257/328cd591-c1e2-6de4-2892-6629bdc73ed6.png)

---
　後日「ごめん、ワンワ・クパも追加で‼︎」とプランナーが言い放ちました。するとどうでしょう。とりあえず~~プランナーにグーを放ち~~上記の方法で管理するとしたら、合計4つ目の変数が出来ますね。このままでは種類が増えると、変数が増えて管理がめんどくさいことになります。
　という様に、上記の方法では変数が敵の種類の数だけ増えることになり、管理しきれません。

![別々に管理2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540257/80a70c68-fffa-7bf1-44a3-fa37fbd689a1.png)
　なので、「別々に管理」ではなく「まとめて管理」をしたいと思いませんか？

# 管理方法
　今回の記事では一部を除きC++11で使えますが、今後の為にもC++17乗り換えましょう(｀･ω･´)

## 下準備
　上記の例では、まず座標・角度・速度などの情報、更新・描画などの関数は共通化出来るので継承するとしましょう。そして、派生クラスに少し変数を追加しましょう。
![別々に管理.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540257/7a8ac7f9-3c0a-4654-ac8b-18890586c394.png)

## ポリモーフィズム
　そして、C++と継承がくれば、ポリモーフィズムが真っ先に思いつきます。※他の言語は知りませんが（笑）
え？ポリモーフィズムって何？それは[この記事](https://jpazamu.com/cpp-polymorphism/ "比較的分かり易いかと思います。え...？ここでは詳しい説明はしませんよ？")を見て下さい。一言で言うなら、「基底クラスを基にして、派生クラスを付け替える」と言う感じでしょうか。もっと知りたい人は各自調べて下さい~~(宿題です)~~。
　兎に角、C++を学び始めて最初に学ぶオブジェクト管理方法はこの方法でしょう。
![ポリモーフィズム.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540257/74ca279b-5c5a-6a61-f04c-7a5a7bcd162a.png)


### 例
　ポリモーフィズム自体の例などは他の記事でいくらでもあるので、ここでは、ポリモーフィズムを使ったオブジェクト管理の例をあげます。

```c++:mian.h
#include <iostream>
#include <memory>
#include <vector>
#include <DirectXMath.h>

struct Base // 基底クラス
{
	DirectX::XMFLOAT2 pos, speed, scale, angle;

	virtual void Update() = 0;
	virtual void Draw() = 0;
};

struct Kuri final /*final: これ以上派生しない事を表す指定子*/
	: public Base
{
	int kuri;
	void Update() override /*override: 基底クラスの純粋仮想関数をオーバーライドする事を示す指定子*/
	{
		std::cout << "struct Kuri Update Func" << std::endl;
	}
	void Draw() override
	{
		std::cout << "struct Kuri Draw Func" << std::endl;
	}
};

struct Nokono final
	: public Base
{
	int nokono;
	void Update() override
	{
		std::cout << "struct Nokono Update Func" << std::endl;
	}
	void Draw() override
	{
		std::cout << "struct Nokono Draw Func" << std::endl;
	}
};

struct Wanwa final
	: public Base
{
	int wanwa;
	void Update() override
	{
		std::cout << "struct Wanwa Update Func" << std::endl;
	}
	void Draw() override
	{
		std::cout << "struct Wanwa Draw Func" << std::endl;
	}
};

struct Kupa final
	: public Base
{
	int kupa;
	void Update() override
	{
		std::cout << "struct Kupa Update Func" << std::endl;
	}
	void Draw() override
	{
		std::cout << "struct Kupa Draw Func" << std::endl;
	}
};

enum class Type // C++ならenumではなくて、enum class を使った方がいいです。
{
	Kuri,
	Nokono,
	Wanwa,
	Kupa,
};
```
　定義部分になっています。「final・override」はC++11以降使える指定子となっています。詳細は[ここ](https://cpprefjp.github.io/lang/cpp11/override_final.html)を見て下さい。

---

```c++:main.cpp
std::vector<std::vector<std::shared_ptr<Base>>> array_enemies;

// 構築
{
	constexpr size_t BuildNum{ 10u }; // 仮構築数

	// struct Kuri
	{
		auto& enemies{ array_enemies.emplace_back() }; // emplace_back()に戻り値があるのはC++17以上からになります。

		enemies.resize(BuildNum); // とりあえず、複数個確保

		for (auto& enemy : enemies)
		{
			enemy = std::make_shared<Kuri>(); // 派生クラスを構築
		}
	}

	// struct Nokono
	{
		auto& enemies{ array_enemies.emplace_back() };

		enemies.resize(BuildNum); // とりあえず、複数個確保

		for (auto& enemy : enemies)
		{
			enemy = std::make_shared<Nokono>(); // 派生クラスを構築
		}
	}

	// struct Wanwa
	{
		auto& enemies{ array_enemies.emplace_back() };

		enemies.resize(BuildNum); // とりあえず、複数個確保

		for (auto& enemy : enemies)
		{
			enemy = std::make_shared<Wanwa>(); // 派生クラスを構築
		}
	}

	// struct Kupa
	{
		auto& enemies{ array_enemies.emplace_back() };

		enemies.resize(BuildNum); // とりあえず、複数個確保

		for (auto& enemy : enemies)
		{
			enemy = std::make_shared<Kupa>(); // 派生クラスを構築
		}
	}
}
```
　オブジェクト管理の構築部分になっています。ここの注意点は「<font color="Red">**生ポインタを使うのではなくスマートポインタを使いましょう**</font>」です。deleteし忘れて、メモリーリークするの嫌ですし[^1]。

[^1]: 自分は生ポインタは絶対に使いたくない症なので、特殊なケースでない限りはスマートポインタを使います。

---

```c++:main.cpp
// 更新
{
	// 特定のクラスを更新
	{
		auto& enemies{ array_enemies[static_cast<size_t>(Type::Kuri)] };

		for (auto& enemy : enemies)
		{
			auto& kuri{ std::dynamic_pointer_cast<Kuri>(enemy) }; // 重要！！

			assert(kuri && "ダウンキャスト失敗");

			kuri->kuri = 10;
		}
	}

	// 全更新
	for (auto& enemies : array_enemies)
	{
		for (auto& enemy : enemies)
		{
			enemy->Update();
		}
	}
}

// 描画
for (auto& enemies : array_enemies)
{
	for (auto& enemy : enemies)
	{
		enemy->Draw();
	}
}
```
　オブジェクト管理の更新などになっています。ここの注意点は「特定のクラスを更新」で、**`auto& kuri{ std::dynamic_pointer_cast<Kuri>(enemy) };`**を使った[**ダウンキャスト**](https://qiita.com/RYO-4947123/items/eaeb48b6fcf97c02710f "大変分かり易い記事です")する必要があるところです。[std::dynamic_pointer_cast](https://cpprefjp.github.io/reference/memory/shared_ptr/dynamic_pointer_cast.html)で失敗した際は、nullptrのstd::shared_ptrが返ります[^2]。
　といっても、この例では``struct Kuri``だと確定しているので、``std::static_pointer_cast<Kuri>(enemy)``としても問題ないとは思います。

[^2]: dynamic_cast自体が[RTTI](https://programming-place.net/ppp/contents/cpp/language/031.html)になってくるので詳しい説明はしません。RTTIは便利なんですが、速度がネックになってきます。やっぱりC++は静的に型を決定する言語だと勝手に思っています。

## void* (``std::shared_ptr<void>``)
 次に、[void*](https://qiita.com/subrutm/items/f8695b5589a0e5402ced)を使った方法を紹介します。といっても、void*の知識としては
> void *型は汎用ポインタと呼ばれ、あらゆるポインタを代入することができます。
https://ota42y.com/blog/2014/11/06/cpp-void-pointer/

の知識さえあれば使う分には問題ありません。もっと知りたいという人は各自調べて下さい[^3]。
![shared_ptr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540257/2e6302e2-f674-18ba-c60f-1886c3732193.png)

[^3]: 投げやり とは言ってはいけません。

### 例
　例を紹介するんですが、内容的には[ポリモーフィズム](#ポリモーフィズム)とあまり変わらないのですが...。ポリモーフィズムと比べて``std::shared_ptr<void>``の利点としては、全く同じ別の基底クラス(えぇ...!?)を使った派生クラスも突っ込める等ぐらいでしょうか。他は...知りません...。(;^ω^)

　紹介します とほざきながら、自分はこの手段をほぼほぼ使ったことがないので「使い方おかしいだろ」ポイントが存在する可能性があるのは申し訳ありません。

```cpp:main.h
/*
  定義部分は同じなので飛ばします
*/
```

```cpp:main.cpp
	std::vector<std::vector<std::shared_ptr<void>>> array_enemies;

    /*
       構築部分は同じなので飛ばします
    */

	// 更新
	{
		// 特定のクラスを更新
		{
			auto& enemies{ array_enemies[static_cast<size_t>(Type::Kuri)] };

			for (auto& enemy : enemies)
			{
				auto& kuri{ std::static_pointer_cast<Kuri>(enemy) }; // 重要！！

				kuri->kuri = 10;
			}
		}

		// 全更新
		for (auto& enemies : array_enemies)
		{
			for (auto& enemy : enemies)
			{
				std::static_pointer_cast<Base>(enemy)->Update();
			}
		}
	}

	// 描画
	for (auto& enemies : array_enemies)
	{
		for (auto& enemy : enemies)
		{
			std::static_pointer_cast<Base>(enemy)->Draw();
		}
	}

```

　といった感じになります。注意点としては「[std::static_pointer_cast](https://cpprefjp.github.io/reference/memory/shared_ptr/static_pointer_cast.html)」を使って特定のクラスにアクセスしているところと、「全更新・描画」の際に``std::static_pointer_cast<Base>(enemy)``を使って``struct Base``の要素にアクセスしているところです。
　特定のクラスにアクセスする際に、[ポリモーフィズム](#ポリモーフィズム)と同じように「std::dynamic_pointer_cast」を使おうとすると、エラーを吐きます。[ロベールさん](http://www7b.biglobe.ne.jp/~robe/cpphtml/html02/cpp02042.html)を見ると
> dynamic_cast では、void* へキャストすることもできます。しかし、<font color="Red">void* からのキャストは行えません</font>。

と書いてあるので、「std::dynamic_pointer_cast」は使えないみたいです。つまり、動的な型変換は不可能ということなのでしょうか。
　といっても、「std::static_pointer_cast」と書いてある通り、処理速度は「std::dynamic_pointer_cast」と比べるとかなり高速なので余り気にしなくてもいい気がします(本当か？)。

# まとめ
　前編では２つの方法を使ったオブジェクト管理を紹介しましたが、どうでしょうか？初心者に役に立てれば幸いです。もう一度言っておきますが、「こんな方法もあるよ」程度に思ってもらえればOKです。
　後半の[void* を使った方法](#void-stdshared_ptrvoid)は「使える」というだけで、オブジェクト管理という点では使いずらいのかも知れません。基本は前半の[ポリモーフィズム](#ポリモーフィズム)を使った方が無難だと思います。
　後編は、C++17の機能をふんだんに使った方法を紹介します[^5]。

今回、簡易的なクラス図を作成するのに使ったのは「[Draw.io](https://www.draw.io/)」というサイトです。すごく使いやすいのでお勧めです。

---

最近になればなるほど 記事が長めになりすぎていた気がするので、短めに書くようにしました。

[^5]: これを機にC++17環境にしましょう！！