前回,とりえあず原始的な実装を考えたけど,実際に使っていくには階層構造
(:「このキャラクタの → このアイテムを → このキャラクタに → 使う」みたいに次々と選んでいくやつ)
を扱うための実装が必要だろう.
……というわけで考えてみる.
「このキャラクタの → このアイテムを → このキャラクタに → 使う」みたいに次々と選んでいくやつ
とかいう話は,「スタックかな?」とか真っ先に思うのでその方向で.
とはいえ,我の拙い経験上,そこで「そしたら std::stack
で!」と安易に飛びつくと可能な操作が限定されすぎていて後で泣くことになるのが目に見えているのですぞ!
……ってことで,とりあえずは std::list
あたりに逃げておくことにしよう.
//スタックに積まれるやつ.
//「メニュー」だけに限定する必要はないので「入力を処理できて,何か描画できるやつ」という話にする
//(まともな名前が全然思いつかない!!)
struct IGUI
{
//更新処理.
//戻り値は未定,引数は操作入力情報
virtual UpdateResult Update( const IController &Controller ) = 0;
//描画処理
virtual void Draw() const = 0;
};
//スタック
struct GUIStack
{
public:
//プッシュ
void Push( std::unique_ptr<IGUI> upGUI ){ m_GUIs.emplace_back( std::move(upGUI) ); }
//更新処理
//スタックのトップにあるやつの Update() を呼ぶ
void Update( const IController &Controller )
{
if( !m_GUIs.empty() )
{ m_GUIs.back().Update( Controller ); }
}
//描画
//スタックに Push された順に Draw() を呼ぶ
void Draw() const
{
for( const auto &upGUI : m_GUIs )
{ upGUI->Draw(); }
}
private:
std::list< std::unique_ptr<IGUI> > m_GUIs; //順序は,最後にPushされたものが back 側
};
とりあえず最初の形としてはこのくらいじゃなイカ?
「引数が std::unique_ptr
の場合は↑のコードみたく値渡しな書き方なのかそれとも
std::unique_ptr<IGUI> &&
って書くのか? どっち?」とかいう実装方法の迷いの話ではなくて,
「 std::unique_ptr
だと Push()
に渡した物の所有権が持ってかれるけど,それOKか?」っていう話.
このへんは以下のように他の型についても考えたんだけども,今現在の結論は「
unique_ptr
でおk」かな,と.
IGUI*
(普通のポインタ):
スタック側では所有権は全く管理しないっていう.
しかしそれだとシンプルに「投げっぱなし( Fire and Forget
)な使い方ができないのが不便そう」だと思った.
(動的に生成してスタックに積んだものを別途どこかで維持管理するってのは,ちょっと考えただけでも大変に面倒そうだ)
shared_ptr
:
「投げっぱなしもできるし,外で寿命を握ることも可能」という意味で悪くない選択に思える.
何もしないカスタムでデリータを持たせるとかすればそのオブジェクトだけは寿命管理の話とは無関係にすることもできる.
しかし後述のように unique_ptr
でも同じようなことは達成できそうだから別に shared_ptr
でなくても良いかな,と.
weak_ptr
:
スタックからのオブジェクト除去タイミングを「そのオブジェクトが無くなっていたら」っていう話にできるが,まぁ不便なことの方が多そう.
普通のポインタと同様に投げっぱなしができないし.
unique_ptr
:
基本は投げっぱなし.
そうしたく無い場合には?っていう点に関しては,例えば
//スタック側で勝手に捨ててほしくないオブジェクトへのポインタをスタックにPushするためのヘルパ
class PtrWrapper : public IGUI
{
private:
*m_pObj;
IGUI public:
( IGUI *pObj ) : m_pObj(pObj) {}
PtrWrapper
//IGUIの実装は単に m_pObj に処理を委譲する
virtual UpdateResult Update( const IController &Controller ) override { return m_pObj->Update( Controller ); }
virtual void Draw() const override { m_pObj->Draw(); }
};
みたいなのをスタックに Push すればよさそう.
//何かこんなのがあって……
m_XXX; //普通にメンバに抱えているオブジェクト(XXXはIGUIを継承した型)
XXX m_Stack; //階層的処理制御用のスタック
GUIStack
//m_XXXスタックに積む際にはこうする
m_Stack.Push( std::make_unique<PtrWrapper>( &m_XXX ) );
IGUI::Update()
が戻り値で「俺はもう終わり」って言ってきたら m_GUIs
から除去する,っていうルールでいいんじゃないかな.
「階層的なメニュー」を想像すると,とにかく全員の Draw()
を呼ぶ必要があると思う.
しかし,誰かが「画面全体」を占める描画を行う場合,それより先に行われた描画処理は全て無駄ってことになる.
そんな無駄な処理はしたくないが……
Draw()
が何もしないように細工しておくだとか,何か相応に対処すればいいんじゃないかな.必要に応じて IGUI
に各タイミングでスタック側から呼ばれるメソッドを付けておくとよいかも.
(メニューにフォーカスがあるか? みたいなのの制御に使えそう)
前回考えたメニューの実装と,ここで考えた
IGUI
とを繋ぐ型を用意する必要がある.
//スタックに積めるメニュー型
class Menu_As_IGUI : public IGUI
{
/* IGUI::Update() や IGUI::Draw() で先に考えたメニューの実装を使う */
};