DirectXTK の小ネタです。
DirectXTKはMicrosoftが提供しているDirectX11を便利に使うためのライブラリです。
デバイスロスト時の処理の記述が面倒くさかったので自動化してみました。
OnDeviceLost
が面倒くさい
例えとして、 DirectXTKを用いてテクスチャを表示する解説ページ(英語) に則って画像を表示するプログラムを書いてみると下のようになる。
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;
};
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
を使って自動で解放する
デストラクタによって自動的にリソースが解放されることを利用して、下のように改善してみた。
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;
};
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
との一番の違いはヒープからメモリをとってこないということである。(メモリはスタック領域からとってくる)(つまり実行時型情報を用いたポリモーフィズムは使えない)
使った例 ↓
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;
};
void Game::CreateDeviceDependentResources()
{
m_resources.emplace();
// spriteBatch, commonStates, texture の初期化処理
}
void Game::OnDeviceLost()
{
m_resources.reset();
}
2つ目の例と見た目はほぼ変わらないですが、余分なヒープ領域をとってくることはなくなった。
補足
上の例の SpriteBatch
とかについても std::unique_ptr
より std::optional
のほうがよさげではある。
でも公式リファレンスでは謎の std::unique_ptr
推しをしている。これは多分 std::optional
の説明が面倒くさいからだと思う。実際の実行性能もほぼ変わらないし。
まとめ
メンバ変数を任意のタイミングでコンストラクト/デストラクトしたいときは std::optional
!!!