テンプレートを用いたシーン遷移

# はじめに

この記事は[前回の記事](https://qiita.com/o_joh4869/items/a0d23fb26897762c1990)の続きですが、読まなくても理解できる内容にしたつもりです。  

# やりたいこと
各シーンでクラスを作ってそのupdate関数でChangeScene関数を呼ぶだけでシーンを遷移。
メインループではSceneManagerのupdateとdrawを呼ぶ。
関数テンプレートの明示的インスタンス化とextern templateを利用して遷移先のクラスの宣言のみでシーンを遷移。
共有データクラスをテンプレート引数に指定して管理させる。

# extern template周り

```cpp:f.hpp
template<typename T>
void f() {
	//型Tの定義が必要な処理
}
```

という関数テンプレートに対し、

```cpp
class Hoge;
extern template void f<Hoge>();
```

または

```cpp
extern template void f<class Hoge>();
```
とすることで、```class Hoge```の定義なしに関数テンプレートを呼び出すことができる。

extern templateは暗黙のインスタンス化を禁止しているだけなので、```class Hoge```の定義を知っている別ファイルで明示的インスタンス化を行う必要がある。

```cpp:Hoge.cpp
#include"f.hpp"
class Hoge {

};
template void f<Hoge>();
```

この機能を用いて、シーン遷移を次のシーンのクラスの宣言のみで行う。

# 基底クラス

とりあえず決まっている情報で基底クラスを作成する。
今回は仮想関数を利用してポリモーフィズムを実現する。
後の都合上、基底クラスは前回の記事とは異なり```Scene```名前空間を導入して```BaseT```という名前を使用する。
(説明の都合上今後名前空間を省いて書くことがある)

```cpp
namespace Scene {

	template<typename CommonData>
	class BaseT {
	protected:
		BaseT() : 
			mCommonData(nullptr)//仮
		{

		}

	public:
		virtual ~BaseT() = default;

		//コピー禁止
		BaseT(BaseT &) = delete;
		BaseT &operator(BaseT &) = delete;

		virtual void update() = 0;
		virtual void draw() const = 0;

	private:
		//共有データ
		CommonData *mCommonData;

	protected:
		auto getData() {
			return mCommonData;
		}

	protected:
		template<typename T>
		void ChangeScene() {
			//シーンを遷移する処理
		}

	};
}
```

# 共有データのポインタコピー
共有データのポインタは各シーンで頻繁にアクセスすることが予想されるためメンバにコピーして持っておきたい。
今後ほかにもコピーしたいものが出てくるかもしれないので、初期化データクラス```InitData```を```BaseT```のインナークラスとして定義してその参照を渡すことで解決する。

```cpp
template<typename CommonData>
class BaseT {
public:
	class InitData {
	public:
		CommonData *data;
	};
protected:
	BaseT(const InitData &init) : 
		mCommonData(init.data)
	{
	}

	//...
};
```

# SceneManagerクラス

シーン全体を管理するクラスを作成する。
共有データの管理(```new```, ```delete```)と、各シーンの管理を行う。
後の都合上```BaseT```のインナークラスとして定義する。

```cpp
template<typename CommonData>
class BaseT {
	//...

public:
	class Manager {
		public:
			Manager() : 
				mCommonData(new CommonData),
				mScene(nullptr)
			{

			}

			~Manager() {
				if( mCommonData != nullptr ) mCommonData;
				if( mScene != nullptr ) delete mScene;
			}

			void update() {
				mScene->update();
				//仮
			}

			void draw() const {
				mScene->draw();
			}

		private:
			CommonData *mCommonData;
			BaseT *mScene;
	};

	//...
};
```

# 共有データがない場合の対処

共有データなしで作りたい場合も当然存在する。
```BaseT```のテンプレート引数に```void```を渡すことで共有データなしの状態を作りたい。

```cpp
template<typename CommonData = void> //voidをデフォルトに
class BaseT {

};
```

```new CommonData```以外は```mCommonData```に```nullptr```を代入しておけば解決する。
これは関数テンプレートの明示的特殊化で解決できる。

```cpp
template<typename CommonData>
class BaseT {
	//...

public:
	class Manager {
	private:
		template<typename T>
		CommonData *mMakeData() {
			return new T;
		}

		template<>
		CommonData *mMakeData() {
			return nullptr;
		}

	public:
		Manager() : 
			mCommonData(mMakeData<CommonData>()),
			mScene(nullptr)
		{
		}
		
		//...
	};

	//...
};
```



# 静的メンバの追加

```ChangeScene``` 関数では遷移先のクラスのインスタンスを作成し、```Manager```に渡す。
共有データのポインタをコピーするには```Manager```のインスタンスにアクセスする必要がある。
```Manager```の静的メンバに自身のポインタを保存する変数を追加して解決する。

```cpp
class Manager {
	//...

private:
	static Manager *manager;
};
```

```cpp
template<typename CommonData>
typename BaseT<CommonData>::Manager *BaseT<CommonData>::Manager::manager = nullptr;
```

これにコンストラクタで```this```ポインタを代入し、デストラクタで```nullptr```に戻す。

```cpp
class Manager {
	Manager() : 
		mCommonData(mMakeData<CommonData>()),
		mScene(nullptr)
	{
		if(manager != nullptr) throw;
		manager = this;
	}

	~Manager() {
		manager = nullptr;
	}
};
```

# Manager::update

```mScene->update()``` の中で```ChangeScene``` 関数が呼ばれる。
ChangeSceneで```mScene```を```delete```してしまうと、メンバ関数の実行中にインスタンスが破棄されてしまう。
(C++の構造上動いてしまうが、書き方によっては内容が保証されないので避けるべき)
そのため、```Manager```に何かしらの方法で遷移することを伝えなければならない。
```Manager```のメンバに```BaseT *mNext```を追加して次のシーンのポインタが入っていたら遷移を実行させるようにする。

```cpp
class Manager {
public:
	Manager() : 
		mCommonData(new CommonData),
		mScene(nullptr),
		mNext(nullptr)
	{
		if( manager != nullptr ) throw;
		manager = this;
	}

	~Manager() {
		if( mCommonData != nullptr ) mCommonData;
		if( mScene != nullptr ) delete mScene;
		if( mNext != nullptr ) delete mNext;
		manager = nullptr;
	}

	void update() {
		mScene->update();
		if( mNext != nullptr ) {
			delete mScene;
			mScene = mNext;
			mNext = nullptr;
		}
	}

	//...
};
```

# ChangeScene関数

```ChangeScene``` 関数では、遷移先のシーンのインスタンスを作成し、```Manager::manager::mNext```に代入したい。
```Manager::manager```や```mNext```は```private```メンバなので```Manager```から```BaseT```をフレンド宣言して解決する。
(ここは無理やり通したので目をつぶってほしい)

```cpp
class Manager {
	//...

	template<typename T>
	friend BaseT;
};
```

後の都合上、```Manager```のメンバにシーンをセットする関数```mSetScene```と、初期化用データを作成する```mGetInit```関数を作成する。

```cpp
class Manager {
	//...

private:
	static void mSetScene(BaseT *p) {
		if( mNext != nullptr ) delete mNext;
		mNext = p;
	}

	static InitData mGetInit() {
		return InitData(mCommonData);
	}
	
	//...
};
```

```ChangeScene``` 関数はこれらの関数を用いて```mNext```にシーンクラスのインスタンスを代入する。

```cpp
template<typename T>
void ChangeScene() {
	T::Manager::mSetScene(new T(T::Manager::mGetInit()));
}
```

# 最初のシーンをセット

最初のシーンを```Manager```にセットするメンバ関数を作成する。
各シーンクラスの定義を必要とする処理を```ChangeScene``` 関数にまとめたいので、```set``` 関数内では```ChangeScene```を呼ぶだけにする。

```cpp
class Manager {
public:
	template<typename T>
	void set() {
		ChageScene<T>();
	}
};
```

```mSetScene``` 関数で、```mScene == nullptr``` の場合、```mNext```ではなく```mScene```に代入させればいい。

```cpp
static void mSetScene(BaseT *p) {
	if( mNext != nullptr ) delete mNext;
	if( mScene == nullptr ) mScene = p;
	else mNext = p;
}
```

あとは、メインループ前で```BaseT<CommonData>::Manager```のインスタンスを作成して```update```関数と```draw```関数を呼べばひとまず完成だ。
扱いやすくするため、```BaseT<CommonData>::Manager``` を```using``` して別名を付けておくといいだろう。

```cpp

namespace Scene {
	//...

	template<typename CommonData = void>
	using Manager = BaseT<CommonData>::Manager;
}
```

```cpp
namespace Scene {
	extern template void BaseT<>::ChangeScene<class Title>();
}

int main() {

	Scene::Manager<> manager;
	manager.set<Scene::Title>();

	while( true ) {
		//メインループ
		manager.update();
		manager.draw();

		//...
	}
}
```

# 階層化

共有データクラスは一つしか登録できないが、一部のシーンクラス内でのみ共有したいデータが出てくることがある。
具体的には、ゲーム画面で敵の挙動変化をシーンクラスの変更で行うと、ゲームの中で使用されるものの多くはタイトル画面では必要ないのがわかる。
ゲーム画面周りのクラスの共有データを登録できるように、シーンの階層化を実装する。

# アイデア
階層ごとに基底クラスを作成し、それを継承してシーンを
階層を管理するクラスを、一つ前の階層の基底クラスをpublic継承して作成する。
一つ前の階層管理クラスにその階層の管理クラスを登録をセットする。
具体的な遷移の内容を書く```ChangeScene``` 関数は遷移先の型情報しかないので、遷移先の基底クラスの静的メンバ関数を用いて遷移を実行する。

# 各階層用の基底クラス

どの階層にいても前の階層の共有データが必要になる場面が存在しうるので前の階層の基底クラスを継承する形で実装する。
また、```BaseT```のテンプレート引数を増やしてデフォルトで```void```を指定することでもとの基底クラスをそのまま用いる。

```cpp
template<typename CommonData = void, typename BellowBase = void>
class BaseT;

template<typename CommonData>
class BaseT<CommonData> {
	//...
};

template<typename CommonData, typename BellowBase>
class BaseT : public BellowBase {
public:
	class InitData {
	public:
		InitData(CommonData *data, const typename BellowBase::InitData &bellowInit) :
			data(data),
			bellowInit(bellowInit)
		{}

		CommonData *data;
		typename BellowBase::InitData bellowInit;
	};
protected:
	BaseT(const InitData &init) :
		BellowBase(init.bellowInit),
		mCommonData(init.data)
	{
	}
public:
	virtual ~BaseT() = default;

	virtual void update() override = 0;
	virtual void draw() const override = 0;

private:
	CommonData *mCommonData;

protected:
	auto getData() {
		return mCommonData;
	}
	template<typename T, typename U>
	friend class BaseT;
};
```

# 階層管理クラス

共有データのポインタ管理(```new```, ```delete```)、階層のシーンクラスの管理を行う。
このクラスは外から用いることがないのでprivateにする。

```cpp
template<typename CommonData, typename BellowBase>
class BaseT : public BellowBase {
	//...

private:
	class Manager : public BellowBase {
	public:
		Manager(const typename BellowBase::InitData &init) :
			BellowBase(init),
			mCommonData(new CommonData),
			mScene(nullptr),
			mNext(nullptr),
			mBellowInit(init)
		{
			if( manager != nullptr ) throw;
			manager = this;
		}

		virtual ~Manager() {
			if( mCommonData != nullptr ) delete mCommonData;
			if( mScene != nullptr ) delete mScene;
			if( mNext != nullptr ) delete mNext;
			manager = nullptr;
		}

		virtual void update() override {
			mScene->update();
			if( mNext != nullptr ) {
				delete mScene;
				mScene = mNext;
				mNext = nullptr;
			}
		}

		virtual void draw() const override {
			mScene->draw();
		}

	private:
		BaseT *mScene;
		BaseT *mNext;
		CommonData *mCommonData;
		typename BellowBase::InitData mBellowInit;

		static Manager *manager;

		static void mSetScene(BaseT *p) {
			if( manager->mNext != nullptr ) delete manager->mNext;
			if( manager->mScene == nullptr ) manager->mScene = p;
			else manager->mNext = p;
		}

		static InitData mGetInit() {
			if(manager == nullptr) BellowBase::Manager::mSetScene(new Manager(BellowBase::Manager::mGetInit()));
			return InitData(manager->mCommonData, manager->mBellowInit);
		}

		template<typename T, typename U>
		friend class BaseT;
	};

	//...
};
```

```ChangeScene``` 関数の中身は変えずにそのまま使えるはずだ。

# 完成形

```cpp:Scene/Template.hpp
#pragma once

namespace Scene {

	template<typename CommonData = void, typename BellowBase = void>
	class BaseT;

	template<typename CommonData>
	class BaseT<CommonData> {
	public:
		class InitData {
		public:
			InitData(CommonData *data) : 
				data(data)
			{
			}

			CommonData *data;
		};

	protected:
		BaseT(const InitData &init) : 
			mCommonData(init.data)
		{

		}

	public:
		virtual ~BaseT() = default;

		//コピー禁止
		BaseT(BaseT &) = delete;
		BaseT &operator=(BaseT &) = delete;

		virtual void update() = 0;
		virtual void draw() const = 0;

	private:
		CommonData *mCommonData;

	protected:
		auto getData() const {
			return mCommonData;
		}
	public:
		class Manager {
		private:
			template<typename T>
			CommonData *mMakeData() {
				return new CommonData;
			}

			template<>
			CommonData *mMakeData<void>() {
				return nullptr;
			}

		public:
			Manager() : 
				mScene(nullptr),
				mNext(nullptr),
				mCommonData(mMakeData<CommonData>())
			{
				if( manager != nullptr ) throw;
				manager = this;
			}

			~Manager() {
				if( mScene != nullptr ) delete mScene;
				if( mNext != nullptr ) delete mNext;
				if( mCommonData != nullptr ) delete mCommonData;
				manager = nullptr;
			}

			void update() {
				mScene->update();
				if( mNext != nullptr ) {
					delete mScene;
					mScene = mNext;
					mNext = nullptr;
				}
			}

			void draw() {
				mScene->draw();
			}

			template<typename T>
			void set() {
				BaseT::ChangeScene<T>();
			}

		private:
			BaseT *mScene;
			BaseT *mNext;
			CommonData *mCommonData;

			static void mSetScene(BaseT *p) {
				if( manager->mNext != nullptr ) delete manager->mNext;
				if( manager->mScene == nullptr ) manager->mScene = p;
				else manager->mNext = p;
			}

			static InitData mGetInit() {
				return InitData(manager->mCommonData);
			}

			static Manager *manager;

			template<typename T, typename U>
			friend class BaseT;

		};
	private:
		

	public:
		template<typename T>
		static void ChangeScene() {
			T::Manager::mSetScene(new T(T::Manager::mGetInit()));
		}
	};

	template<typename CommonData>
	typename BaseT<CommonData>::Manager *BaseT<CommonData>::Manager::manager = nullptr;

	////////////////////////////////////////////////////////////////////////////////

	template<typename CommonData, typename BellowBase>
	class BaseT : public BellowBase {
	public:
		class InitData {
		public:
			InitData(CommonData *data, const typename BellowBase::InitData &bellowInit) :
				data(data),
				bellowInit(bellowInit)
			{}

			CommonData *data;
			typename BellowBase::InitData bellowInit;
		};
	protected:
		BaseT(const InitData &init) :
			BellowBase(init.bellowInit),
			mCommonData(init.data)
		{
		}
	public:
		virtual ~BaseT() = default;

		virtual void update() override = 0;
		virtual void draw() const override = 0;

	private:
		CommonData *mCommonData;

	protected:
		auto getData() {
			return mCommonData;
		}

	private:
		class Manager : public BellowBase {
		public:
			Manager(const typename BellowBase::InitData &init) :
				BellowBase(init),
				mCommonData(new CommonData),
				mScene(nullptr),
				mNext(nullptr),
				mBellowInit(init)
			{
				if( manager != nullptr ) throw;
				manager = this;
			}

			virtual ~Manager() {
				if( mCommonData != nullptr ) delete mCommonData;
				if( mScene != nullptr ) delete mScene;
				if( mNext != nullptr ) delete mNext;
				manager = nullptr;
			}

			virtual void update() override {
				mScene->update();
				if( mNext != nullptr ) {
					delete mScene;
					mScene = mNext;
					mNext = nullptr;
				}
			}

			virtual void draw() const override {
				mScene->draw();
			}

		private:
			BaseT *mScene;
			BaseT *mNext;
			CommonData *mCommonData;
			typename BellowBase::InitData mBellowInit;

			static Manager *manager;

			static void mSetScene(BaseT *p) {
				if( manager->mNext != nullptr ) delete manager->mNext;
				if( manager->mScene == nullptr ) manager->mScene = p;
				else manager->mNext = p;
			}

			static InitData mGetInit() {
				if(manager == nullptr) BellowBase::Manager::mSetScene(new Manager(BellowBase::Manager::mGetInit()));
				return InitData(manager->mCommonData, manager->mBellowInit);
			}

			template<typename T, typename U>
			friend class BaseT;
		};

		template<typename T, typename U>
		friend class BaseT;
	};

	template<typename CommonData, typename BellowBase>
	typename BaseT<CommonData, BellowBase>::Manager *BaseT<CommonData, BellowBase>::Manager::manager;

	////////////////////////////////////////////////////////////////////////////////

	template<typename CommonData = void>
	using Manager = typename BaseT<CommonData>::Manager;
}
```

```cpp:Scene/CommonData.hpp
#pragma once

namespace Scene {
	class CommonData {
	public:
		CommonData();


	};
}
```

```cpp:Scene/Base.hpp
#pragma once
#include"Template.hpp"
#include"CommonData.hpp"

namespace Scene {
	using Base = BaseT<CommonData>;
}
```

```cpp:Title.cpp
#include"Base.hpp"

namespace Scene {
	class Title : public Base {
	public:
		Title(const InitData &init) :
			Base(init),
			a(0)
		{}

		virtual ~Title() = default;

		virtual void update() override;

		virtual void draw() const override;

		int a;
	};

	//遷移先を宣言
	extern template void Base::ChangeScene<class Select>();

	void Title::update() {
		if( a == 120 ) ChangeScene<Select>();
	}

	void Title::draw() const {

	}
	
	//シーンを登録
	template void Base::ChangeScene<Title>();
}
```

# 今後の課題

・遷移クラス(フェードアウトなど)の作成
・遷移クラスを使う人が作成しやすいテンプレートの作成
・遷移間のみの共有データクラスを通じない値の共有
・friend宣言でごまかした部分の見直し
・テンプレートを使った静的な多態を勉強してみたい