DirectXTKでデバイスロスト処理を自動化する

[DirectXTK](https://github.com/microsoft/DirectXTK/wiki) の小ネタです。
DirectXTKはMicrosoftが提供しているDirectX11を便利に使うためのライブラリです。

デバイスロスト時の処理の記述が面倒くさかったので自動化してみました。

# `OnDeviceLost` が面倒くさい

例えとして、 [DirectXTKを用いてテクスチャを表示する解説ページ(英語)](https://github.com/microsoft/DirectXTK/wiki/Sprites-and-textures) に則って画像を表示するプログラムを書いてみると下のようになる。

```C++:Game.h
class Game {
public:
    // ...
    void CreateDeviceDependentResources();
    void OnDeviceLost();
private:
    // ...
    std::unique_ptr<DirectX::SpriteBatch> m_spriteBatch;
    std::unique_ptr<DirectX::CommonStates> m_commonStates;
    Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_texture;
};
```

```C++:Game.cpp
void Game::CreateDeviceDependentResources()
{
    // m_spriteBatch, m_commonStates, m_texture の初期化処理
}

void Game::OnDeviceLost()
{
    m_spriteBatch.reset();
    m_commonStates.reset();
    m_texture.Reset();
}
```

この中の `OnDeviceLost` に注目してほしい。
これはデバイスロスト時に呼ばれる関数ですべてのリソースを解放する必要があるのだが、リソースを追加するたびに `OnDeviceLost` にそのリソースの解放処理を追記しないといけない。これは面倒くさく、バグにつながる。

できればリソースを追加しただけで解放も自動的に行ってくれるようにしたい。

# `std::unique_ptr` を使って自動で解放する

デストラクタによって自動的にリソースが解放されることを利用して、下のように改善してみた。

```C++:Game.h
class Game {
public:
    // ...
    void CreateDeviceDependentResources();
    void OnDeviceLost();
private:
    // ...
    struct DeviceDependentResources {
        std::unique_ptr<DirectX::SpriteBatch> spriteBatch;
        std::unique_ptr<DirectX::CommonStates> commonStates;
        Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> texture;
    };
    std::unique_ptr<DeviceDependentResources> m_resources;
};
```

```C++:Game.cpp
void Game::CreateDeviceDependentResources()
{
    m_resources = std::make_unique<DeviceDependentResources>();
    // spriteBatch, commonStates, texture の初期化処理
}

void Game::OnDeviceLost()
{
    m_resources.reset();
}
```

リソースをすべて `DeviceDependentResources` 構造体に入れてそれを `std::make_unique` している。
解放時は `DeviceDependentResources` 構造体ごと解放する。
これによってリソースを増やしたときに `OnDeviceLost` に解放処理を追記する必要が無くなった。やったー。

とても便利になったが、一つ問題があるとすれば、ヒープ領域を確保する必要が無かったところヒープを使用してしまっていることだ。
デストラクタによる自動解放を利用しつつ、新たにヒープ領域を確保しない方法があればいいのだが……。と思っていたが、あった。

# `std::optional` を使おう

`std::optional` はC++17で追加された、無効かもしれない値を保持するためのものである。
これを使えば先ほどの `std::unique_ptr` を用いたような任意のタイミングでのコンストラクトとデストラクトが可能になる。
そして、 `std::unique_ptr` との一番の違いはヒープからメモリをとってこないということである。(メモリはスタック領域からとってくる)(つまり実行時型情報を用いたポリモーフィズムは使えない)

使った例 ↓

```C++:Game.h
class Game {
public:
    // ...
    void CreateDeviceDependentResources();
    void OnDeviceLost();
private:
    // ...
    struct DeviceDependentResources {
        std::unique_ptr<DirectX::SpriteBatch> spriteBatch;
        std::unique_ptr<DirectX::CommonStates> commonStates;
        Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> texture;
    };
    std::optional<DeviceDependentResources> m_resources;
};
```

```C++:Game.cpp
void Game::CreateDeviceDependentResources()
{
    m_resources.emplace();
    // spriteBatch, commonStates, texture の初期化処理
}

void Game::OnDeviceLost()
{
    m_resources.reset();
}
```

2つ目の例と見た目はほぼ変わらないですが、余分なヒープ領域をとってくることはなくなった。

# 補足

上の例の `SpriteBatch` とかについても `std::unique_ptr` より `std::optional` のほうがよさげではある。
でも[公式リファレンス](https://github.com/microsoft/DirectXTK/wiki)では謎の `std::unique_ptr` 推しをしている。これは多分 `std::optional` の説明が面倒くさいからだと思う。実際の実行性能もほぼ変わらないし。

# まとめ

メンバ変数を任意のタイミングでコンストラクト/デストラクトしたいときは `std::optional`！！！

# 参考

- [DirectXTK公式wiki](https://github.com/microsoft/DirectXTK/wiki)
- [`std::optional`の日本語リファレンス](https://cpprefjp.github.io/reference/optional/optional.html)