テンプレートで楽をしよう
さて今回はいきなりですがまず Fig.1 を見てください
Fig.1
struct IBaseObject { virtual void DeleteMe() = 0; virtual bool QuerySupport( OBJECT_CLASS_ID clsid ) = 0; virtual IBaseObject* GetClassObject( OBJECT_CLASS_ID clsid ) = 0; ・・・ }; struct IDerivedObject1 : public IBaseObject { virtual bool DoSomething1( ・・・ ) = 0; ・・・ } struct IDerivedObject2 : public IBaseObject { virtual bool DoSomething2( ・・・ ) = 0; ・・・ } struct IDerivedObject3 : public IBaseObject { virtual bool DoSomething3( ・・・ ) = 0; ・・・ }
ここでこの IBaseObject というインターフェイスの仕様を下記のように決めましょう
- IBaseObject::DeleteMe
- 自分自身のインスタンスを破棄する
- IBaseObject::QuerySupport
- 指定されたクラス識別子が示す派生クラスをサポートするなら trueを返す。サポートしないなら falseを返す
- このオブジェクトが指定された派生クラスをサポートする場合 IBaseObject::GetClassObjectメソッドによってその派生クラスのインスタンス(を指すポインタ)を得ることができる
- IBaseObject::GetClassObject
また、クラス識別子とクラスは一意に対応付けられるものとします
ここまではいいでしょうか
さて、仕様が決まったところで今回はこれをサンプルに話を進めるとしましょう
ここでまず気付いて欲しいのはこの仕様自体に問題があるという点ですが、仕様の問題はひとまず横に置いて、こんな仕様のインターフェイスを使う場合のことを考えてみてください
例えばそれは Fig.2 のようになるでしょうか
Fig.2
int cChildren = pParentObject->GetNumberOfChildren(); for( int n = 0; n < cChildren; ++n ){ IBaseObject* pBaseObject = pParentObject->GetChildObject( n ); if( pBaseObject->QuerySupport( classid_of_IDerivedObject1 )){ IDerivedObject1* pDerivedObject1 = ( IDerivedObject1* )pBaseObject->GetClassObject( classid_of_IDerivedObject1 ); pDerivedObject1->DoSomething1( ・・・ ); if( pDerivedObject1 != pBaseObject ){ pDerivedObject1->DeleteMe(); } } }
これは誰でもこんな風に使いますよね
って、ここで「pParentObjectってなんだ!?」とかそいうことは気にしないでくださいね。話の流れには関係ないですから
では次に Fig.3 を見てください
Fig.3
int cChildren = pParentObject->GetNumberOfChildren(); for( int n = 0; n < cChildren; ++n ){ IBaseObject* pBaseObject = pParentObject->GetChildObject( n ); if( pBaseObject->QuerySupport( classid_of_IDerivedObject1 )){ IDerivedObject1* pDerivedObject1 = ( IDerivedObject1* )pBaseObject->GetClassObject( classid_of_IDerivedObject1 ); if( !pDerivedObject1->DoSomething1( ・・・ )){ break; // 失敗したら中断する。 } if( pDerivedObject1 != pBaseObject ){ pDerivedObject1->DeleteMe(); } } }
この場合はどうでしょうか?
これでいいでしょうか
これではダメですよね。break したときにインスタンスを破棄するのを忘れていますね
ですからこれは Fig.4 のように修正しましょうか
Fig.4
int cChildren = pParentObject->GetNumberOfChildren(); for( int n = 0; n < cChildren; ++n ){ IBaseObject* pBaseObject = pParentObject->GetChildObject( n ); if( pBaseObject->QuerySupport( classid_of_IDerivedObject1 )){ IDerivedObject1* pDerivedObject1 = ( IDerivedObject1* )pBaseObject->GetClassObject( classid_of_IDerivedObject1 ); if( !pDerivedObject1->DoSomething1( ・・・ )){ if( pDerivedObject1 != pBaseObject ){ pDerivedObject1->DeleteMe(); } break; // 失敗したら中断する。 } if( pDerivedObject1 != pBaseObject ){ pDerivedObject1->DeleteMe(); } } }
この場合はどうでしょうか?
これもちょっとイヤですよね。同じ処理が2箇所もあるのがなんかイヤじゃないですか?
では Fig.5 のように修正したらどうでしょうか
Fig.5
int cChildren = pParentObject->GetNumberOfChildren(); for( int n = 0; n < cChildren; ++n ){ IBaseObject* pBaseObject = pParentObject->GetChildObject( n ); if( pBaseObject->QuerySupport( classid_of_IDerivedObject1 )){ IDerivedObject1* pDerivedObject1 = ( IDerivedObject1* )pBaseObject->GetClassObject( classid_of_IDerivedObject1 ); BOOL fSuccess = pDerivedObject1->DoSomething1( ・・・ ); if( pDerivedObject1 != pBaseObject ){ pDerivedObject1->DeleteMe(); } if( fSuccess ){ break; // 失敗したら中断する。 } } }
この場合はどうでしょうか?
これならいいですか?
処理としてはこれで問題なさそうですが、面倒ですね
しかしこの面倒な処理を自動化できる仕組みがC++にはあります
Fig.6 のようなテンプレートを書くことを考えてみましょう:
Fig.6
template<class T,OBJECT_CLASS_ID clsid> class CSmartObject { IBaseObject* m_pBaseObject; T m_pDerivedObject; void Assign( IBaseObject* pBaseObjectNew ){ if( pBaseObjectNew != m_pBaseObject ){ Release(); m_pBaseObject = pBaseObjectNew; if( m_pBaseObject && m_pBaseObject->QuerySupport( clsid )){ m_pDerivedObject = ( T )m_pBaseObject->GetClassObject( clsid ); } } } void Release(){ if( m_pDerivedObject &&( m_pBaseObject != m_pDerivedObject )){ m_pDerivedObject->DeleteMe(); m_pDerivedObject = NULL; } m_pBaseObject = NULL; } public: CSmartObject& operator=( IBaseObject* pBaseObject ){ Assign( pBaseObject ); return *this; } T operator->() const { return m_pDerivedObject; } operator T() const { return m_pDerivedObject; } CSmartObject( IBaseObject* pBaseObject = NULL ): m_pBaseObject(NULL), m_pDerivedObject(NULL) { Assign( pBaseObject ); } ~CSmartObject(){ Release(); } };
このテンプレートを使うと Fig.5 のコードは Fig.7 のように書けます
Fig.7
int cChildren = pParentObject->GetNumberOfChildren(); for( int n = 0; n < cChildren; ++n ){ CSmartObject<IDerivedObject1*,classid_of_IDerivedObject1> pDerivedObject1 = pParentObject->GetChildObject( n ); if( pDerivedObject1 ){ if( !pDerivedObject1->DoSomething1( ・・・ )){ break; } } }
どうでしょうか?
随分すっきりしましたよね
この場合、問題は「スコープを抜けるときにメモリを解放し忘れてしまう」ってところですよね
「スコープを抜けるときに」とくれば当然「デストラクタ」と頭に浮かぶのがC++的ってものです
ところで、自分で設計する時にはこの例で使っている GetClassObjectみたいな仕様にしちゃダメですよ。これはただ例のために使ってるだけですからね
どういったところが問題なのか、これは次回への宿題にしましょう
それから、この CSmartObject<>というテンプレートクラスの使い方には制限がありますよ
どんな制限なのか、これも次回への宿題にしましょうか
あー。もう1つ
クラスとクラス識別子の対応は一意なわけですから CSmartObject
できれば CSmartObject
これもあわせて次回への宿題にしましょう