テンプレートで楽をしよう

さて今回はいきなりですがまず 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
    • クラス識別子で指定された派生クラスのインターフェイスをサポートするならその派生クラスのインスタンス(を指すポインタ)を返す。サポートしないなら NULLを返す
    • このとき返される派生クラスのインスタンスのアドレスが基底クラスのインスタンスのアドレスと同じでなかったら、(それは内部で新たにアロケートしたものということで、)呼び出し側は責任を持ってその派生クラスのインスタンスを解放する必要がある

また、クラス識別子とクラスは一意に対応付けられるものとします
ここまではいいでしょうか
さて、仕様が決まったところで今回はこれをサンプルに話を進めるとしましょう

ここでまず気付いて欲しいのはこの仕様自体に問題があるという点ですが、仕様の問題はひとまず横に置いて、こんな仕様のインターフェイスを使う場合のことを考えてみてください
例えばそれは 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で済ませたいですよね
これもあわせて次回への宿題にしましょう