スマートポインタの罠 〜CComPtr の場合〜

 C++ の標準ライブラリでも御存知の通りスマートポインタなるものがあります。
このスマートポインタとはガベージコレクション(不要になったポインタの自動破棄)がない C++ でかなり重宝します。
そのスマートポインタの COM 用のものが CComPtr です。
こいつはオブジェクトが破棄されると同時に COM の解放メソッドの IUnknown::Release () を呼び出してくれる代物ですが
こいつが結構曲者でして使い方がちょっとややこしかったりします。
この現象はゲーム作成で使用する DirectX の時のみだけかもしれません(他の状況では確かめていません…)。
どういう事かと言いますが COM コンポーネントをこいつに渡す時のメソッドです。
この CComPtr<T> には operator= が定義されているのでこいつを使えばいいのかと思ったのですがこれが間違い…。
関数の実装を覗いてみると…なんと内部で IUnknown::AddRef () が呼ばれてしまうではありませんかっ!!
DirectX のコンポーネントは作成されてポインタが返された時点で IUnknown::AddRef () が呼び出し済みなはず※1なので
一回分多く IUnknown::AddRef () が呼び出されてしまい、結果的にデストラクタで呼び出される IUnknown::Release () では
解放出来ない状態になっています。_| ̄|○
それを回避するには CComPtr<T> の基底クラスの CComPtrBase<T> が持つ CComPtrBase<T>::Attach () を使用します。
こいつは単に現在保持している COM ポインタがある場合はそれの IUnknown::Release () を呼び出して代わりに指定された
ポインタを単に代入するだけです。
DirectX 系のオブジェクトには必ずこの CComPtrBase<T>::Attach () を使用します。
※1なぜ呼び出し済みかといいますと通常スマートポインタを使わない場合でもよく見かける SAFE_RELEASE マクロから分かるようにポインタが NULL 以外の場合は IUnknown::Release () を呼び出しポインタに NULL にしてしまうと言う事。作成後に IUnknown::Release () は一度しか呼び出されていない。
具体的にソースにすると以下の様になります。
// Direct3D オブジェクトの場合
CComPtr <IDirect3D9>  direct3d_;
IDirect3D9 *  direct3d = NULL;

// これが正しい代入方法
direct3d = ::Direct3DCreate9 (D3D_SDK_VERSION);
direct3d_.Attach (direct3d);

// 間違った代入方法
direct3d = ::Direct3DCreate9 (D3D_SDK_VERSION);
direct3d_ = direct3d;


// Direct3DVertexBuffer9 の場合
CComPtr <IDirect3DVertexBuffer9>  vertex_buffer_;
IDIrect3DVertexBuffer9 *  vertex_buffer = NULL;

// これが正しい代入方法
direct3d_device->CreateVertexBuffer (..., &vertex_buffer, ...);
vertex_buffer_.Attach (vertex_buffer);

// 間違った代入方法
direct3d_device->CreateVertexBuffer (..., &vertex_buffer, ...);
vertex_buffer_ = vertex_buffer;