コールバックの真価

前回、「関数のポインタ」を使って「コールバック」について考えてきましたが、「コールバック」は何も「関数のポインタ」でなければ実現できないわけではありません
今回は他の方法も見てみましょう
そして「コールバック」の真価へと迫りましょう

関数オブジェクト

さて、まずは「関数のポインタ」の代わりに「関数オブジェクト」を使う方法を見てみましょう
「関数オブジェクト」とは何でしょうか
これは言い換えると「関数呼び出し演算子を持つ任意の型のインスタンス」となるでしょうか
関数呼び出し演算子とは operator() のことです
では「関数オブジェクト」を使った EnumChildObjects を考えてみましょう

前回の例の3つの関数(PrintObjectNames, CountObjects, QueryObjectByName)の実装にはそれぞれ EnumChildObjects に対して異なる関数のポインタを渡していたという点に着目してください
つまり関数ポインタ版の EnumChildObjects には任意の関数のポインタを渡せるようになっていたのでした
では関数オブジェクト版の EnumChildObjects ではどうすればいいでしょうか
それはもうお分かりですね
関数オブジェクト版の EnumChildObjects には任意の関数オブジェクトを渡せるようにすればいいのです
ここで「任意の関数オブジェクト」とは、つまり「関数呼び出し演算子を持つ任意の型のインスタンス」という意味ですね
「任意の型」というところに着目してください
型を指定するにはテンプレートを使うんでしたね
さあ、では実装を考えてみましょう
Fig.1

template<class T> void	EnumChildObjects( ISampleObject* pObject, T	callback ){
	for( int	n = 0; n < pObject->GetNumberOfChildren(); ++n ){
		ISampleObject*	pChildObject = pObject->GetChildObject( n );
		if( !callback( pChildObject )){
			break;
		}
	}
}

ではこの新しい関数オブジェクト版の EnumChildObjects を使って前回の例の3つの関数を実装してみましょう
例えば次のようになるでしょうか
Fig.2

struct printobjects_each {
	bool	operator()( ISampleObject* pObject ){
		char	szName[MAX_SAMPLE_OBJECT_NAME];
		pObject->GetName( elementsof( szName ), szName );
		printf( "%s\n", szName );
		EnumChildObjects( pObject, *this );
		return	true;
	}
};
void	PrintObjectNames( ISampleObject* pObject ){
	EnumChildObjects( pObject, printobjects_each());
}
class countobjects_each {
	int*	m_pcObjects;
public:
	bool	operator()( ISampleObject* pObject ){
		++(*m_pcObjects);
		EnumChildObjects( pObject, *this );
		return	true;
	}
	countobjects_each( int* pcObjects ):
	m_pcObjects(pcObjects)
	{
	}
};
int	CountObjects( ISampleObject* pObject ){
	int cObjects = 0;
	EnumChildObjects( pObject, countobjects_each( &cObjects ));
	return	cObjects;
}
class queryobjectbyname_each {
	const char* m_pszName;
	ISampleObject**	m_ppObject;
public:
	bool	operator()( ISampleObject* pObject ){
		char	szName[MAX_SAMPLE_OBJECT_NAME];
		pObject->GetName( elementsof( szName ), szName );
		if( !lstrcmp( m_pszName, szName )){
			*m_ppObject = pObject;
		}
		else{
			EnumChildObjects( pObject, *this );
		}
		return	!!*m_ppObject;
	}
	queryobjectbyname_each( const char* pszName, ISampleObject**	ppObject ):
	m_pszName(pszName),
	m_ppObject(ppObject)
	{
	}
};
ISampleObject*	QueryObjectByName( ISampleObject*	pObject, const char*	pszName ){
	ISampleObject*	p = NULL;
	EnumChildObjects( pObject, queryobjectbyname_each( pszName, &p ));
	return	p;
}

あるいは EnumChildObjects に渡す関数オブジェクトを参照渡しにして次のようでもいいかもしれません
Fig.3

template<class T> void	EnumChildObjects( ISampleObject* pObject, T&	callback ){
	for( int	n = 0; n < pObject->GetNumberOfChildren(); ++n ){
		ISampleObject*	pChildObject = pObject->GetChildObject( n );
		if( !callback( pChildObject )){
			break;
		}
	}
}
class countobjects_each {
	int	 m_cObjects;
public:
	bool	operator()( ISampleObject* pObject ){
		++m_cObjects;
		EnumChildObjects( pObject, *this );
		return	true;
	}
	int result(){
		return	m_cObjects;
	}
	
	countobjects_each():
	m_cObjects(0)
	{
	}
};
int	CountObjects( ISampleObject* pObject ){
	countobjects_each	callback;
	EnumChildObjects( pObject, callback );
	return	callback.result();
}
class queryobjectbyname_each {
	const char* m_pszName;
	ISampleObject*	m_pObject;
public:
	bool	operator()( ISampleObject* pObject ){
		char	szName[MAX_SAMPLE_OBJECT_NAME];
		pObject->GetName( elementsof( szName ), szName );
		if( !lstrcmp( m_pszName, szName )){
			m_pObject = pObject;
		}
		else{
			EnumChildObjects( pObject, *this );
		}
		return	!!m_pObject;
	}
	ISampleObject*	result(){
		return	m_pObject;
	}
	
	queryobjectbyname_each( const char* pszName ):
	m_pszName(pszName),
	m_pObject(NULL)
	{
	}
};
ISampleObject*	QueryObjectByName( ISampleObject*	pObject, const char*	pszName ){
	queryobjectbyname_each	callback( pszName );
	EnumChildObjects( pObject, callback );
	return	callback.result();
}

インターフェイス

さて、前回の「関数のポインタ」を使う方法と今回の「関数オブジェクト」を使う方法と二つの方法を紹介しましたが「コールバック」に使える方法はまだこれだけではありません
インターフェイス」を使う方法もあります
ここで言う「インターフェイス」とは「抽象基底クラス(ABC)」のことです
覚えていますか?
さあ、では「インターフェイス」を使った新しい EnumChildObjects の実装を考えてみましょう
Fig.4

struct IEnumObjectsCallback {
	virtual int	OnEnum( ISampleObject*	pObject ) = 0;
};
void	EnumChildObjects( ISampleObject* pObject, IEnumObjectsCallback*	pCallback ){
	for( int	n = 0; n < pObject->GetNumberOfChildren(); ++n ){
		ISampleObject*	pChildObject = pObject->GetChildObject( n );
		if( !pCallback->OnEnum( pChildObject )){
			break;
		}
	}
}

ではまたこの新しいインターフェイス版の EnumChildObjects を使って前回の例の3つの関数を実装してみましょう
それは例えば次のようになるでしょうか
Fig.5

struct CEnumObjectsCallback_PrintObjectNames : public IEnumObjectsCallback {
	int	OnEnum( ISampleObject* pObject ){
		char	szName[MAX_SAMPLE_OBJECT_NAME];
		pObject->GetName( elementsof( szName ), szName );
		printf( "%s\n", szName );
		EnumChildObjects( pObject, this );
		return	TRUE;
	}
};
void	PrintObjectNames( ISampleObject* pObject ){
	CEnumObjectsCallback_PrintObjectNames	callback;
	EnumChildObjects( pObject, &callback );
}
class CEnumObjectsCallback_CountObjects : public IEnumObjectsCallback {
	int*	m_pcObjects;
public:
	int	OnEnum( ISampleObject* pObject ){
		++(*m_pcObjects);
		EnumChildObjects( pObject, this );
		return	TRUE;
	}
	CEnumObjectsCallback_CountObjects( int* pcObjects ):
	m_pcObjects(pcObjects)
	{
	}
};
int	CountObjects( ISampleObject* pObject ){
	int cObjects = 0;
	CEnumObjectsCallback_CountObjects	callback( &cObjects );
	EnumChildObjects( pObject, &callback );
	return	cObjects;
}
class CEnumObjectsCallback_QueryObjectByName : public IEnumObjectsCallback {
	const char* m_pszName;
	ISampleObject**	m_ppObject;
public:
	int	OnEnum( ISampleObject* pObject ){
		char	szName[MAX_SAMPLE_OBJECT_NAME];
		pObject->GetName( elementsof( szName ), szName );
		if( !lstrcmp( m_pszName, szName )){
			*m_ppObject = pObject;
		}
		else{
			EnumChildObjects( pObject, this );
		}
		return	!!*m_ppObject;
	}
	CEnumObjectsCallback_QueryObjectByName( const char* pszName, ISampleObject**	ppObject ):
	m_pszName(pszName),
	m_ppObject(ppObject)
	{
	}
};
ISampleObject*	QueryObjectByName( ISampleObject* pObject, const char*	pszName ){
	ISampleObject*	p = NULL;
	CEnumObjectsCallback_QueryObjectByName	callback( pszName, &p );
	EnumChildObjects( pObject, &callback );
	return	p;
}

あるいはまた次のようにしてもいいかもしれません
Fig.6

class CEnumObjectsCallback_CountObjects : public IEnumObjectsCallback {
	int m_cObjects;
public:
	int	OnEnum( ISampleObject* pObject ){
		++m_cObjects;
		EnumChildObjects( pObject, this );
		return	TRUE;
	}
	int result(){
		return	m_cObjects;
	}
	
	CEnumObjectsCallback_CountObjects():
	m_cObjects(0)
	{
	}
};
int	CountObjects( ISampleObject* pObject ){
	CEnumObjectsCallback_CountObjects	callback;
	EnumChildObjects( pObject, &callback );
	return	callback.result();
}
class CEnumObjectsCallback_QueryObjectByName : public IEnumObjectsCallback {
	const char* m_pszName;
	ISampleObject*	m_pObject;
public:
	int	OnEnum( ISampleObject* pObject ){
		char	szName[MAX_SAMPLE_OBJECT_NAME];
		pObject->GetName( elementsof( szName ), szName );
		if( !lstrcmp( m_pszName, szName )){
			m_pObject = pObject;
		}
		else{
			EnumChildObjects( pObject, this );
		}
		return	!!m_pObject;
	}
	CEnumObjectsCallback_QueryObjectByName( const char* pszName ):
	m_pszName(pszName),
	m_pObject(NULL)
	{
	}
};
ISampleObject*	QueryObjectByName( ISampleObject*	pObject, const char*	pszName ){
	CEnumObjectsCallback_QueryObjectByName	callback( pszName );
	EnumChildObjects( pObject, &callback );
	return	callback.result();
}

コールバックの本質

さて、ここまで「コールバック」に使える方法として「関数のポインタ」「関数オブジェクト」「インターフェイス」と3つの方法を紹介しました
もうお分かりと思いますが「コールバック」というものは1つのモデルであって、「関数のポインタ」も「関数オブジェクト」も「インターフェイス」も、それはただの実装のテクニックなのです
では「コールバック」の本質とは何でしょうか
それを考えるためにまた前回の例の3つの関数に戻りましょう
この3つの関数はそれぞれ内部で EnumChildObjects という関数を呼び出しています
そしてそこにそれぞれが自分の都合に合った「コールバック関数/オブジェクト/インターフェイス」を渡していますね
これは何を意味しているのでしょうか
EnumChildObjects という関数自体は実に単純な機能の関数です
それは「指定のオブジェクトの子要素を列挙する」というだけの機能しか持ちません
しかしここに「コールバック」という機構があることで、なぜか様々な仕事ができています
これは何を意味しているのでしょうか
ここにこそ「コールバック」の本質があります
「コールバック」の本質とは「柔軟なブラックボックス」あるいは「カスタマイズ可能なブラックボックス」を提供できる点にある。と言えるでしょうか
あるいは逆にその柔軟性によって様々な処理を「ブラックボックス」の中に閉じ込めることができる点にある。と言えるでしょうか

というところで今回はここまでにしましょう。ではまた次回