EngineのAccessorはなぜ参照を返すのか
EngineのSubsystem Accessorを、値やPointerではなく参照で返している理由を整理した。
背景
現在のEngineでは、各SubsystemへのAccessorを参照で返している。
class Engine
{
public:
RenderSystem& Renderer();
PhysicsSystem& Physics();
InputSystem& Input();
private:
RenderSystem m_renderSystem;
PhysicsSystem m_physicsSystem;
InputSystem m_inputSystem;
};
例えばRenderSystemへのAccessorは次のようになる。
RenderSystem& Engine::Renderer()
{
return m_renderSystem;
}
ここで疑問になるのが、次の点。
なぜ値やPointerではなく、参照を返しているのか
値で返す場合
まず、値で返すと次のようになる。
RenderSystem Engine::Renderer()
{
return m_renderSystem;
}
この場合、呼び出し側にはm_renderSystemそのものではなく、別のRenderSystemが返される形になる。
auto renderer = m_engine.Renderer();
これでは、Engineが所有しているRenderSystemを直接操作したいという目的と合わない。
また、RenderSystemのようなSubsystemは、内部にDirectX 12のResourceや状態を持つ。そのため、そもそも簡単にコピーできるObjectとして扱いたくない。
Pointerで返す場合
次にPointerで返す方法もある。
RenderSystem* Engine::Renderer()
{
return &m_renderSystem;
}
利用側では、
m_engine.Renderer()->Render();
と書ける。この方法でも、Engineが所有しているRenderSystemへ直接アクセスできる。
ただし、Pointerにはnullptrを表現できるという性質がある。そのため、Interfaceとして見ると、
RenderSystem* Renderer();
は、次のようにも読める。
RenderSystemが存在しない可能性がある
利用側でも、本来は次の可能性を考える必要が出てくる。
if (m_engine.Renderer() != nullptr)
{
m_engine.Renderer()->Render();
}
今回はSubsystemが必ず存在する
現在のEngineでは、RenderSystemをMemberとして直接所有している。
class Engine
{
private:
RenderSystem m_renderSystem;
};
つまり、Engineが存在しているなら、m_renderSystemも存在する。Renderer()を呼んだ結果、nullptrが返る状態は想定していない。
そこで、
RenderSystem& Renderer();
とすることで、「このAccessorは必ず有効なRenderSystemを返す」という前提をInterfaceにも表現する。
参照なら所有権も移動しない
Accessorを利用する側は、RenderSystemを所有したいわけではない。所有しているのはあくまでEngine。
Engine
└─ owns RenderSystem
利用側は、そのObjectを一時的に使いたいだけ。
m_engine.Renderer().Render();
参照を返すことで、
- Objectをコピーしない
- 所有権を移動しない
- Engineが持つ同じObjectへアクセスする
という関係になる。
参照なら何でもよいわけではない
ただし、常に参照を返せばよいわけではない。もしSubsystemが、
- 初期化前には存在しない
- 実行中に破棄される
- Optionalな機能である
- 利用できない状態が正常に存在する
という設計なら、Pointerなど別の表現を検討する必要がある。
今回参照を使っているのは、次の現在の設計があるから。
EngineのLifetime中、Subsystemが常に存在する
まとめ
EngineのAccessorを参照で返しているのは、
RenderSystem& Renderer();
というInterfaceで、次の前提を表したいから。
- EngineがSubsystemを所有している
- Subsystemをコピーしない
- 所有権を呼び出し側へ渡さない
- Subsystemは必ず存在する
- Engineが所有する同じInstanceを操作する
そのため、現在の設計では値返しやPointer返しより、参照返しが意図に合っている。