DirectX11 スマートポインタ活用 BoostとC++11

これまでプログラムしていて、これは危険かもしれないと感じるソースコードがありました。

	ID3D11Texture2D* back_buff = NULL;
	hr = g_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&back_buff );
	if( FAILED( hr ) ){ return hr; }

	hr = g_pd3dDevice->CreateRenderTargetView( back_buff, NULL, &g_pRenderTargetView );
	back_buff->Release();//GetBufferで取得、使用後はRelease
	if( FAILED( hr ) ){ return hr; }

これのback_buff->Release();
GetBufferで取得したテクスチャを使用後Releaseが必要ですが、書き忘れる自信があります。というか忘れてました。単純なプログラムであればVisualStuioのデバッグですぐにリークが検出され修正個所もすぐに発見できますが、複雑になってくるとそうはいきません。
というわけで、スマートポインタをつかって自動的にRelease関数を呼ぶようにします。

使用するスマートポインタ
Boost C++ライブラリのboost::intrusive_ptrを使用。boostでスマートポインタといえばshared_ptrですが、DirectXのオブジェクト(COMオブジェクト)は、すでに参照管理する関数AddRef、Releaseが用意されているため実装が単純なboost::intrusive_ptrを採用しました。

boostとC++11
http://www.boost.org/からダウンロード。ver1.53。
リンクが必要な機能を使わないので、インクルードパスを設定するだけ準備完了。
使う必要はないけど、ちょっとC++11を使ってみる。nullptrとdecltype。一番使ってみたかったエイリアステンプレートは未対応(2013/3/2)。

intrusive_ptrの定義

#include <boost/intrusive_ptr.hpp>

//DX11(COM)オブジェクトポインタ
template <typename TYPE>
struct DXPtr
{
	typedef boost::intrusive_ptr<typename TYPE> Type;
};

void intrusive_ptr_add_ref(IUnknown* p) { p->AddRef(); }
void intrusive_ptr_release(IUnknown* p) { p->Release(); }

//C++11 未対応 エイリアステンプレート
//template <typename TYPE>
//using DXPtr = boost::intrusive_ptr<TYPE>;

intrusive_ptr_add_refとintrusive_ptr_releaseで参照管理の処理を定義する。boost::intrusive_ptr<ID3D11Texture2D>と書くと長いのでDXPtrというクラスを定義して短くする。DXPtr<ID3D11Texture2D>::Type。それとboostが使えなくなった場合に独自実装に切り替えられるようにしておく。

使ってみる1

	ID3D11Texture2D* back_buff = NULL;
	hr = g_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&back_buff );
	if( FAILED( hr ) ){ return hr; }
	DXPtr<ID3D11Texture2D>::Type sp_backbuff(back_buff,false);//すでにAddRefされているのでfalse

なんか状況が悪化している気がする。余計に面倒だし。生ポインタのback_buffを間違って使ってしまいそう。この時点でスマートポインタを使うことを断念しそうになるが、解決方法を考えてみる。問題は、ポインタのポインタでの結果受け取りと一時的に定義した生ポインタの誤使用。
intrusive_ptrの内部ポインタで直接結果を受け取る方法は危険すぎるので却下。

使ってみる2
次のようなテンプレートとマクロを使って問題を解決。

template <typename TYPE>
class GSetDXPtr : boost::noncopyable
{
public:
	GSetDXPtr(TYPE& ptr) : pObj(nullptr), rPtr(ptr){}
	~GSetDXPtr()
	{
		rPtr.swap(TYPE(pObj,false));
	}
	typename TYPE::element_type** operator()(){return &pObj;}
private:
	typename TYPE::element_type* pObj;
	TYPE& rPtr;
};

#define GSET_DXPTR(_dxptr) GSetDXPtr<std::decay<decltype(_dxptr)>::type>(_dxptr)()

//使用例
	DXPtr<ID3D11Texture2D>::Type back_buff;
	hr = g_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ),
					( LPVOID* )GSET_DXPTR(back_buff) );
	
//GSET_DXPTR未使用
	hr = g_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ),
					( LPVOID* )GSetDXPtr<DXPtr<ID3D11Texture2D>::Type>(back_buff)() );

関数呼び出し時に作成された一時オブジェクトは関数の評価(実行)後に削除されるという仕組みを使って問題を解決しました。一時オブジェクトGSetDXPtrがGetBufferの結果を受け取り、GetBuffer実行完了後に呼ばれるGSetDXPtrデストラクタで構築時に指定したintrusive_ptrに結果が代入される。ただし、そのまま使うとものすごく長くなるのでマクロとdecltype(C++11)を使って記述を簡略化。
これならなんとか使えそうです。でもなにか落とし穴がありそうでこわい。

ここまで作ってみたけれど
ID3D11Texture2D**をDXPtr<ID3D11Texture2D>::Type&にしたラップ関数を定義して使えばいいのでは。

サンプルダウンロード DirectX11スマートポインタ

追記(2013/4/1)
GSET_DXPTRに配列を指定するとdecltype(配列[])は参照型になるためコンパイルエラー。std::decayを使用して通常の型に変換することで解決。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

認証:数字を入力してください(必須) * Time limit is exhausted. Please reload CAPTCHA.