2014年2月15日追記:Visual C++ 2012でこの問題は修正されました。参考:CComPtrのバグが直っていた
ATL::CComPtrをCOMインタフェース以外のクラスで使うのは要注意です。具体的には、1つ目のVTableがIUnknownで始まっていない場合、代入演算子が正しく動作しません。
#define _ATL_NO_AUTOMATIC_NAMESPACE #include <windows.h> #include <atlbase.h> struct Foo { virtual void f() = 0; }; struct Bar : public Foo, public IUnknown // struct Bar : public IUnknown, public Foo // これだと問題ない { virtual void f() override {}; // 以下の実装は本題ではないので中身は適当に省略。 virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**) throw() override { return E_NOTIMPL; }; virtual ULONG STDMETHODCALLTYPE AddRef() throw() override { return 1; } virtual ULONG STDMETHODCALLTYPE Release() throw() override { return 1; } }; int main() { ATL::CComPtr<Bar> bar1(new Bar); // 動く ATL::CComPtr<Bar> bar2 = bar1; // 動く ATL::CComPtr<Bar> bar3; bar3 = bar1; // アウト } |
この理由は、CComPtrの代入演算子の実装を見ると分かります。Visual C++ 2010ではatlcomcli.hの328行目です。
return static_cast<T*>(AtlComPtrAssign((IUnknown**)&p, lp)); |
pというのは、CComPtrのメンバ変数です。そして、AtlComPtrAssignはこんな関数です。
IUnknown* AtlComPtrAssign(IUnknown** pp, IUnknown* lp); |
やっていることはほぼ*pp = lpです(正確には、それに加えて適切にAddRef/Releaseを呼びます)。
AtlComPtrAssignの2番目の実引数でIUnknownへのアップキャストが発生し、それをそのままpに代入することになっているのが原因です。複数の基底クラスそれぞれに対応するVTableが作られるため、Bar b;とあったとき、(intptr_t)&b != (intptr_t)(IUnknown*)&bとなるからです。この辺りのからくりはC++: 水面下の仕組み(Visual C++ 6のドキュメント)が参考になります。
「基底クラスを指定する順番に気をつけよう」では再発が怖いので、今後COMインタフェース以外はこのような心配のないboost::intrusive_ptrを使おうと思いました。
スポンサード リンク |