dynamic_castを使わないRTTIの実装
RTTI は実行時型情報(RunTime Type Information)あるいは実行時型識別(RunTime Type Identification)と呼ばれる仕組みです
C++でこれをサポートするための構文が dynamic_cast ですが、これはC++の言語に特有の機能ですからこれをそのままC言語などから利用するには無理があります
今回はC言語などからも RTTI の仕組みを使えるようにする方法について考えてみましょう
C言語からも使えるようにするのですから、何か dynamic_cast に相当するような動作をするメソッドを提供する必要があるでしょう
そのメソッドの仕様を考えてみると次のようになるでしょうか:
- 型を直接指定することはできないので、何か型と一意に関連付けられた識別子を使ってクラスを指定する
- 指定されたクラスを基底クラスに持っていればキャストできる
- 基底クラス同士に関係が無くてもキャストできる
- 共通の基底クラスはただ1つに決まっている
最も単純な実装は次の Fig.1 ように仮想継承を使ったものになるでしょうか
Fig.1
#define classid_CSampleUnknown 0 #define classid_CSampleFoo 1 #define classid_CSampleBar 2 #define classid_CSampleFooBar 3 class CSampleUnknown { public: virtual void* DynamicCast( int classid ){ void* pvObject = NULL; if( classid == classid_CSampleUnknown ){ pvObject = this; } return pvObject; } CSampleUnknown() { } virtual ~CSampleUnknown(){ } }; class CSampleFoo : virtual public CSampleUnknown { int m_foo; public: virtual void* DynamicCast( int classid ){ void* pvObject = NULL; if( classid == classid_CSampleFoo ){ pvObject = this; } else{ pvObject = CSampleUnknown::DynamicCast( classid ); } return pvObject; } virtual int foo(){ return m_foo; } CSampleFoo( int foo ): CSampleUnknown(), m_foo(foo) { } virtual ~CSampleFoo(){ } }; class CSampleBar : virtual public CSampleUnknown { int m_bar; public: virtual void* DynamicCast( int classid ){ void* pvObject = NULL; if( classid == classid_CSampleBar ){ pvObject = this; } else{ pvObject = CSampleUnknown::DynamicCast( classid ); } return pvObject; } virtual int bar(){ return m_bar; } CSampleBar( int bar ): CSampleUnknown(), m_bar(bar) { } virtual ~CSampleBar(){ } }; class CSampleFooBar : virtual public CSampleFoo, virtual public CSampleBar { public: virtual void* DynamicCast( int classid ){ void* pvObject = NULL; if( classid == classid_CSampleFooBar ){ pvObject = this; } else{ pvObject = CSampleFoo::DynamicCast( classid ); if( !pvObject ) pvObject = CSampleBar::DynamicCast( classid ); } return pvObject; } CSampleFooBar( int foo, int bar ): CSampleFoo(foo), CSampleBar(bar) { } virtual ~CSampleFooBar(){ } };
使用例:
CSampleFooBar* pFooBar = new CSampleFooBar( 123, 456 ); CSampleFoo* pFoo = ( CSampleFoo* )pFooBar->DynamicCast( classid_CSampleFoo ); CSampleBar* pBar = ( CSampleBar* )pFooBar->DynamicCast( classid_CSampleBar ); CSampleFoo* pFoo2 = ( CSampleFoo* )pBar->DynamicCast( classid_CSampleFoo ); CSampleBar* pBar2 = ( CSampleBar* )pFoo->DynamicCast( classid_CSampleBar ); CSampleUnknown* pUnknownFoo = ( CSampleUnknown* )pFoo->DynamicCast( classid_CSampleUnknown ); CSampleUnknown* pUnknownBar = ( CSampleUnknown* )pBar->DynamicCast( classid_CSampleUnknown ); CSampleUnknown* pUnknown = pFooBar->DynamicCast( classid_CSampleUnknown ); printf( "pFooBar = 0x%p, foo = %3d, bar = %d\n", pFooBar, pFooBar->foo(), pFooBar->bar()); printf( "pFoo = 0x%p, foo = %3d\n", pFoo, pFoo ? pFoo->foo() : 0 ); printf( "pBar = 0x%p, bar = %3d\n", pBar, pBar ? pBar->bar() : 0 ); printf( "pFoo2 = 0x%p, foo = %3d\n", pFoo2, pFoo2 ? pFoo2->foo() : 0 ); printf( "pBar2 = 0x%p, bar = %3d\n", pBar2, pBar2 ? pBar2->bar() : 0 ); printf( "pUnknownFoo = 0x%p, pUnknownFoo->AddRef() = %d\n", pUnknownFoo, pUnknownFoo ? pUnknownFoo->AddRef() : 0 ); printf( "pUnknownBar = 0x%p, pUnknownBar->AddRef() = %d\n", pUnknownBar, pUnknownBar ? pUnknownBar->AddRef() : 0 ); printf( "pUnknown = 0x%p, pUnknown->AddRef() = %d\n", pUnknown, pUnknown ? pUnknown->AddRef() : 0 ); printf( "pFooBar->AddRef() = %d\n", pFooBar->AddRef());
出力の例:
pFooBar = 0x01091B98, foo = 123, bar = 456 pFoo = 0x01091BA4, foo = 123 pBar = 0x01091BB0, bar = 456 pFoo2 = 0x01091BA4, foo = 123 pBar2 = 0x01091BB0, bar = 456 pUnknownFoo = 0x01091B9C, pUnknownFoo->AddRef() = 2 pUnknownBar = 0x01091B9C, pUnknownBar->AddRef() = 3 pUnknown = 0x01091B9C, pUnknown->AddRef() = 4 pFooBar->AddRef() = 5
動作としてはこれでよさそうです
しかしC言語から使うことを考えるとベタなC++のクラスのままではダメですね
そこでC言語からも使えるように考えてみると次のような注意が必要でしょう:
- 抽象基底クラスを使う。ただし、抽象基底クラスには多重継承を使わない
- 仮想継承を使わない
これを踏まえてまず抽象基底クラスを考えると Fig.2 のようになるでしょうか
Fig.2
#define classid_ISampleUnknown 0 #define classid_ISampleFoo 1 #define classid_ISampleBar 2 #define classid_ISampleFooBar 3 struct ISampleUnknown { virtual void* DynamicCast( int classid ) = 0; virtual unsigned long AddRef() = 0; virtual unsigned long Release() = 0; }; struct ISampleFoo : public ISampleUnknown { virtual int foo() = 0; }; struct ISampleBar : public ISampleUnknown { virtual int bar() = 0; }; struct ISampleFooBar : public ISampleUnknown { virtual int foo() = 0; virtual int bar() = 0; };
ではこれを実装してみましょう
Fig.3
class CSampleFooBar : public ISampleFooBar, public ISampleFoo, public ISampleBar { unsigned long m_cRef; int m_foo; int m_bar; public: virtual void* DynamicCast( int classid ){ void* pvObject = NULL; if( classid == classid_IUnknown ){ // 別に ISampleFooBar じゃなくて ISampleFoo でも ISampleBar でも同じ。 pvObject = ( ISampleUnknown* )( ISampleFooBar* )this; } else if( classid == classid_ISampleFooBar ){ pvObject = ( ISampleFooBar* )this; } else if( classid == classid_ISampleFoo ){ pvObject = ( ISampleFoo* )this; } else if( classid == classid_ISampleBar ){ pvObject = ( ISampleBar* )this; } return pvObject; } virtual unsigned long AddRef(){ return ++m_cRef; } virtual unsigned long Release(){ if( --m_cRef == 0 ){ delete this; return 0; } return m_cRef; } virtual int foo(){ return m_foo; } virtual int bar(){ return m_bar; } CSampleFooBar( int foo ): m_cRef(1), m_foo(foo), m_bar(bar) { } virtual ~CSampleFooBar(){ } };
使用例:
ISampleFooBar* pFooBar = new CSampleFooBar( 123, 456 ); ISampleFoo* pFoo = ( CSampleFoo* )pFooBar->DynamicCast( classid_ISampleFoo ); ISampleBar* pBar = ( CSampleBar* )pFooBar->DynamicCast( classid_ISampleBar ); ISampleFoo* pFoo2 = ( CSampleFoo* )pBar->DynamicCast( classid_ISampleFoo ); ISampleBar* pBar2 = ( CSampleBar* )pFoo->DynamicCast( classid_ISampleBar ); ISampleUnknown* pUnknownFoo = ( CSampleUnknown* )pFoo->DynamicCast( classid_ISampleUnknown ); ISampleUnknown* pUnknownBar = ( CSampleUnknown* )pBar->DynamicCast( classid_ISampleUnknown ); ISampleUnknown* pUnknown = pFooBar->DynamicCast( classid_ISampleUnknown ); printf( "pFooBar = 0x%p, foo = %3d, bar = %d\n", pFooBar, pFooBar->foo(), pFooBar->bar()); printf( "pFoo = 0x%p, foo = %3d\n", pFoo, pFoo ? pFoo->foo() : 0 ); printf( "pBar = 0x%p, bar = %3d\n", pBar, pBar ? pBar->bar() : 0 ); printf( "pFoo2 = 0x%p, foo = %3d\n", pFoo2, pFoo2 ? pFoo2->foo() : 0 ); printf( "pBar2 = 0x%p, bar = %3d\n", pBar2, pBar2 ? pBar2->bar() : 0 ); printf( "pUnknownFoo = 0x%p, pUnknownFoo->AddRef() = %d\n", pUnknownFoo, pUnknownFoo ? pUnknownFoo->AddRef() : 0 ); printf( "pUnknownBar = 0x%p, pUnknownBar->AddRef() = %d\n", pUnknownBar, pUnknownBar ? pUnknownBar->AddRef() : 0 ); printf( "pUnknown = 0x%p, pUnknown->AddRef() = %d\n", pUnknown, pUnknown ? pUnknown->AddRef() : 0 ); printf( "pFooBar->AddRef() = %d\n", pFooBar->AddRef());
出力の例
pFooBar = 0x01091B98, foo = 123, bar = 456 pFoo = 0x01091BA4, foo = 123 pBar = 0x01091BB0, bar = 456 pFoo2 = 0x01091BA4, foo = 123 pBar2 = 0x01091BB0, bar = 456 pUnknownFoo = 0x01091B9C, pUnknownFoo->AddRef() = 2 pUnknownBar = 0x01091B9C, pUnknownBar->AddRef() = 3 pUnknown = 0x01091B9C, pUnknown->AddRef() = 4 pFooBar->AddRef() = 5
このような感じになるでしょうか
Fig.1 のように基底クラスの実装を別にすると、それは Fig.4 のようになります
Fig.4
class CSampleFoo : public ISampleFoo { unsigned long m_cRef; int m_foo; public: virtual void* DynamicCast( int classid ){ void* pvObject = NULL; if( classid == classid_IUnknown ){ pvObject = ( ISampleUnknown* )( ISampleFooBar* )this; } else if( classid == classid_ISampleFoo ){ pvObject = ( ISampleFooBar* )this; } return pvObject; } virtual unsigned long AddRef(){ return ++m_cRef; } virtual unsigned long Release(){ if( --m_cRef == 0 ){ delete this; return 0; } return m_cRef; } virtual int foo(){ return m_foo; } CSampleFoo( int foo ): m_cRef(1), m_foo(foo) { } virtual ~CSampleFoo(){ } }; class CSampleBar : public ISampleBar { unsigned long m_cRef; int m_bar; public: virtual void* DynamicCast( int classid ){ void* pvObject = NULL; if( classid == classid_IUnknown ){ pvObject = ( ISampleUnknown* )( ISampleFooBar* )this; } else if( classid == classid_ISampleBar ){ pvObject = ( ISampleFooBar* )this; } return pvObject; } virtual unsigned long AddRef(){ return ++m_cRef; } virtual unsigned long Release(){ if( --m_cRef == 0 ){ delete this; return 0; } return m_cRef; } virtual int bar(){ return m_bar; } CSampleBar( int bar ): m_cRef(1), m_bar(bar) { } virtual ~CSampleBar(){ } }; class CSampleFooBar : public ISampleFooBar, public CSampleFoo, public CSampleBar { unsigned long m_cRef; public: virtual void* DynamicCast( int classid ){ void* pvObject = NULL; if( classid == classid_IUnknown ){ pvObject = ( ISampleUnknown* )( ISampleFooBar* )this; } else if( classid == classid_ISampleFooBar ){ pvObject = ( ISampleFooBar* )this; } else{ pvObject = CSampleFoo::DynamicCast( classid ); if( !pvObject ){ pvObject = CSampleBar::DynamicCast( classid ); } } return pvObject; } virtual unsigned long AddRef(){ // 別に自前でやらなくても CSampleFoo か CSampleBar の実装を使っても同じ。 return ++m_cRef; } virtual unsigned long Release(){ // 別に自前でやらなくても CSampleFoo か CSampleBar の実装を使っても同じ。 if( --m_cRef == 0 ){ delete this; return 0; } return m_cRef; } virtual int foo(){ return CSampleFoo::foo(); } virtual int bar(){ return CSampleBar::bar(); } CSampleFooBar( int foo ): m_cRef(1), CSampleFoo(foo), CSampleBar(bar) { } virtual ~CSampleFooBar(){ } };
使用例:
ISampleFooBar* pFooBar = new CSampleFooBar( 123, 456 ); ISampleFoo* pFoo = ( CSampleFoo* )pFooBar->DynamicCast( classid_ISampleFoo ); ISampleBar* pBar = ( CSampleBar* )pFooBar->DynamicCast( classid_ISampleBar ); ISampleFoo* pFoo2 = ( CSampleFoo* )pBar->DynamicCast( classid_ISampleFoo ); ISampleBar* pBar2 = ( CSampleBar* )pFoo->DynamicCast( classid_ISampleBar ); ISampleUnknown* pUnknownFoo = ( CSampleUnknown* )pFoo->DynamicCast( classid_ISampleUnknown ); ISampleUnknown* pUnknownBar = ( CSampleUnknown* )pBar->DynamicCast( classid_ISampleUnknown ); ISampleUnknown* pUnknown = pFooBar->DynamicCast( classid_ISampleUnknown ); printf( "pFooBar = 0x%p, foo = %3d, bar = %d\n", pFooBar, pFooBar->foo(), pFooBar->bar()); printf( "pFoo = 0x%p, foo = %3d\n", pFoo, pFoo ? pFoo->foo() : 0 ); printf( "pBar = 0x%p, bar = %3d\n", pBar, pBar ? pBar->bar() : 0 ); printf( "pFoo2 = 0x%p, foo = %3d\n", pFoo2, pFoo2 ? pFoo2->foo() : 0 ); printf( "pBar2 = 0x%p, bar = %3d\n", pBar2, pBar2 ? pBar2->bar() : 0 ); printf( "pUnknownFoo = 0x%p, pUnknownFoo->AddRef() = %d\n", pUnknownFoo, pUnknownFoo ? pUnknownFoo->AddRef() : 0 ); printf( "pUnknownBar = 0x%p, pUnknownBar->AddRef() = %d\n", pUnknownBar, pUnknownBar ? pUnknownBar->AddRef() : 0 ); printf( "pUnknown = 0x%p, pUnknown->AddRef() = %d\n", pUnknown, pUnknown ? pUnknown->AddRef() : 0 ); printf( "pFooBar->AddRef() = %d\n", pFooBar->AddRef());
出力の例:
pFooBar = 0x01091B98, foo = 123, bar = 456 pFoo = 0x01091BA4, foo = 123 pBar = 0x01091BB0, bar = 456 pFoo2 = 0x01091BA4, foo = 123 pBar2 = 0x01091BB0, bar = 456 pUnknownFoo = 0x01091B9C, pUnknownFoo->AddRef() = 2 pUnknownBar = 0x01091B9C, pUnknownBar->AddRef() = 3 pUnknown = 0x01091B9C, pUnknown->AddRef() = 4 pFooBar->AddRef() = 5
ただ、このように基底クラスの実装を別に書くとなると、この例の ISampleUnknown のような共通の基底クラスの実装をいくつも書く必要が出てきて、少し面倒ですね
このような場合には、テンプレートクラスを基底クラスに使うと少し楽になりますよ
Fig.5
template<class T,int _classid> class CSampleUnknown : public T { protected: ISampleUnknown* m_pUnkOuter; unsigned long m_cRef; public: virtual void* DynamicCast( int classid ){ void* pvObject = NULL; if( classid == classid_ISampleUnknown ){ pvObject = ( ISampleUnknown* )this; } else if( classid == _classid ){ pvObject = ( T* )this; } else if( m_pUnkOuter ){ pvObject = m_pUnkOuter->DynamicCast( classid ); } return pvObject; } virtual unsigned long AddRef(){ return ++m_cRef; } virtual unsigned long Release(){ if( --m_cRef == 0 ){ delete this; return 0; } return m_cRef; } CSampleUnknown( ISampleUnknown* pUnkOuter = NULL ): m_pUnkOuter(pUnkOuter), m_cRef(1) { if( m_pUnkOuter ){ m_pUnkOuter->AddRef(); } } virtual ~CSampleUnknown(){ if( m_pUnkOuter ){ m_pUnkOuter->Release(); m_pUnkOuter = NULL; } } }; class CSampleFoo : public CSampleUnknown<ISampleFoo,classid_ISampleFoo> { int m_foo; public: virtual int foo(){ return m_foo; } CSampleFoo( int foo, ISampleUnknown* pUnkOuter = NULL ): CSampleUnknown<ISampleFoo,classid_ISampleFoo>(pUnkOuter), m_foo(foo) { } virtual ~CSampleFoo(){ } }; class CSampleBar : public CSampleUnknown<ISampleBar,classid_ISampleBar> { int m_bar; public: virtual int bar(){ return m_bar; } CSampleBar( int bar, ISampleUnknown* pUnkOuter = NULL ): CSampleUnknown<ISampleBar,classid_ISampleBar>(pUnkOuter), m_bar(bar) { } virtual ~CSampleBar(){ } }; class CSampleFooBar : public CSampleUnknown<ISampleFooBar,classid_ISampleFooBar>, public CSampleFoo, public CSampleBar { public: virtual void* DynamicCast( int classid ){ void* pvObject = CSampleUnknown<ISampleFooBar,classid_ISampleFooBar>::DynamicCast( classid ); if( !pvObject ){ pvObject = CSampleFoo::DynamicCast( classid ); if( !pvObject ){ pvObject = CSampleBar::DynamicCast( classid ); } } return pvObject; } // テンプレートクラスを基底クラスに使っているので書いていないけれども、 // 実際には CSampleUnknown<ISampleFooBar,classid_ISampleFooBar> の実装がインライン展開されるので // こう(↓)書いてあるのと同じ。 /* virtual unsigned long AddRef(){ return CSampleUnknown<ISampleFooBar,classid_ISampleFooBar>::AddRef(); } virtual unsigned long Release(){ return CSampleUnknown<ISampleFooBar,classid_ISampleFooBar>::Release(); } */ // 本当にこう(↑)書いても文法的も機能的にも問題ないようです。 virtual int foo(){ return CSampleFoo::foo(); } virtual int bar(){ return CSampleBar::bar(); } CSampleFooBar( int foo, int bar ): CSampleUnknown<ISampleFooBar,classid_ISampleFooBar>(), CSampleFoo(foo), CSampleBar(bar) { } virtual ~CSampleFooBar(){ } };
使用例:
ISampleFooBar* pFooBar = new CSampleFooBar( 123, 456 ); ISampleFoo* pFoo = ( CSampleFoo* )pFooBar->DynamicCast( classid_ISampleFoo ); ISampleBar* pBar = ( CSampleBar* )pFooBar->DynamicCast( classid_ISampleBar ); ISampleFoo* pFoo2 = ( CSampleFoo* )pBar->DynamicCast( classid_ISampleFoo ); ISampleBar* pBar2 = ( CSampleBar* )pFoo->DynamicCast( classid_ISampleBar ); ISampleUnknown* pUnknownFoo = ( CSampleUnknown* )pFoo->DynamicCast( classid_ISampleUnknown ); ISampleUnknown* pUnknownBar = ( CSampleUnknown* )pBar->DynamicCast( classid_ISampleUnknown ); ISampleUnknown* pUnknown = pFooBar->DynamicCast( classid_ISampleUnknown ); printf( "pFooBar = 0x%p, foo = %3d, bar = %d\n", pFooBar, pFooBar->foo(), pFooBar->bar()); printf( "pFoo = 0x%p, foo = %3d\n", pFoo, pFoo ? pFoo->foo() : 0 ); printf( "pBar = 0x%p, bar = %3d\n", pBar, pBar ? pBar->bar() : 0 ); printf( "pFoo2 = 0x%p, foo = %3d\n", pFoo2, pFoo2 ? pFoo2->foo() : 0 ); printf( "pBar2 = 0x%p, bar = %3d\n", pBar2, pBar2 ? pBar2->bar() : 0 ); printf( "pUnknownFoo = 0x%p, pUnknownFoo->AddRef() = %d\n", pUnknownFoo, pUnknownFoo ? pUnknownFoo->AddRef() : 0 ); printf( "pUnknownBar = 0x%p, pUnknownBar->AddRef() = %d\n", pUnknownBar, pUnknownBar ? pUnknownBar->AddRef() : 0 ); printf( "pUnknown = 0x%p, pUnknown->AddRef() = %d\n", pUnknown, pUnknown ? pUnknown->AddRef() : 0 ); printf( "pFooBar->AddRef() = %d\n", pFooBar->AddRef());
出力の例:
pFooBar = 0x01091B98, foo = 123, bar = 456 pFoo = 0x01091BA4, foo = 123 pBar = 0x01091BB0, bar = 456 pFoo2 = 0x01091BA4, foo = 123 pBar2 = 0x01091BB0, bar = 456 pUnknownFoo = 0x01091B9C, pUnknownFoo->AddRef() = 2 pUnknownBar = 0x01091B9C, pUnknownBar->AddRef() = 3 pUnknown = 0x01091B9C, pUnknown->AddRef() = 4 pFooBar->AddRef() = 5
どうでしょうか、これでC++の RTTI や仮想継承を使わずに自前で RTTI を実装できました
これならC++からもC言語などからも使うことができますし、使い勝手の面でも dynamic_cast と然程変わりありませんね
ところで、この例で使った ISampleUnknownインターフェイスはリファレンスカウンタを持っていますから、DynamicCast でインスタンスのコピーを作ったときにはリファレンスカウンタを増やすのもいいかもしれません
そのようなものを考えるとそれは Fig.6 のようになるでしょうか
Fig.6
struct ISampleUnknown { virtual bool DynamicCast( int classid, void** ppvObject ) = 0; virtual unsigned long AddRef() = 0; virtual unsigned long Release() = 0; }; template<class T,int _classid> class CSampleUnknown : public T { protected: ISampleUnknown* m_pUnkOuter; unsigned long m_cRef; public: virtual bool DynamicCast( int classid, void** ppvObject ){ if( ppvObject ){ *ppvObject = NULL; } bool fSuccess = true; if( !ppvObject ){ fSuccess = false; } if( fSuccess ){ if( classid == classid_ISampleUnknown ){ AddRef(); *ppvObject = ( ISampleUnknown* )this; } else if( classid == _classid ){ AddRef(); *ppvObject = ( T* )this; } else if( m_pUnkOuter ){ fSuccess = m_pUnkOuter->DynamicCast( classid, ppvObject ); } else{ fSuccess = false; } } return fSuccess; } virtual unsigned long AddRef(){ return ++m_cRef; } virtual unsigned long Release(){ if( --m_cRef == 0 ){ delete this; return 0; } return m_cRef; } CSampleUnknown( ISampleUnknown* pUnkOuter = NULL ): m_pUnkOuter(pUnkOuter), m_cRef(1) { if( m_pUnkOuter ){ m_pUnkOuter->AddRef(); } } virtual ~CSampleUnknown(){ if( m_pUnkOuter ){ m_pUnkOuter->Release(); m_pUnkOuter = NULL; } } };
さて、ここで
もうお気付きの方もいらっしゃると思いますが、この ISampleUnknownインターフェイスの DynamicCastメソッドの名前を QueryInterface と変えてみるとどうでしょうか
そうです。これでは全く COM の IUnknownインターフェイスそのものではないですか
そうなんです。これは単なる見た目だけの問題ではなく本当に IUnknownそのものなのです
とかく分からないと言われがちな IUnknown::QueryInterface なのですが、単なる dynamic_cast だと考えてみれば簡単ではないでしょうか
また dynamic_cast と考えることで「QueryInterface の返すオブジェクトは必ず this でなければいけない」といった決まりにも納得がいくのではないでしょうか
というところで、今回はこの辺で