作業ログ

EngineのAccessorはなぜ参照を返すのか

EngineのSubsystem Accessorを、値やPointerではなく参照で返している理由を整理した。

  • game-engine
  • software-design
  • cpp

背景

現在の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返しより、参照返しが意図に合っている。