このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
新しく作った 2 つのクラスにもクラスオブジェクトが必要だ。
これも HardLinkIconIDClass をコピーして作ることができる。
■HardLinkIconIDClass クラス
http://mixi.jp/view_diary.pl?id=277138945&owner_id=2300658
HardLinkIconIDClass を見てみると、実装のほとんどが、
IClassFactory のための一般的なもので、
クラス固有の部分は、新しい HardLinkIconID の
インスタンスを作成する部分だけだ。
なので、HardLinkIconIDClass のコードのうち、
「HardLinkIconID」のクラス名を別のクラスに変えるだけで、
別のクラス用のクラスオブジェクトを作成することができる。
このようなケースの場合、クラスオブジェクトを
クラス毎に作成するより効率の良い方法がある。
それは、「テンプレート」と呼ばれる C++ 機能である。
テンプレートを使うと、メンバや変数の型が異なるだけで、
同じ枠組みやアルゴリズムを持つクラスを定義する際に、
そのテンプレートとなるクラスを 1 つだけ定義するだけで、
それを元に好きな型を使ったクラスを自動生成させ、
簡単に量産することができるようになる。
テンプレートは、C++ の機能なので、
定義や実体の場所を考える必要はなく、
クラス定義もメンバ定義も、
全てヘッダファイルに記述することになる。
さて、各クラスのクラスオブジェクトの違いは、
新しいインスタンスを new する際のクラス名だけだ。
このクラス型をテンプレートパラメータとして、
「ClassObject」テンプレートクラスを作ることにしよう。
========== ClassObject.hpp ==========
#ifndef classobject_hpp_included
#define classobject_hpp_included
// IClassFactory
#include <unknwn.h>
// std::nothrow
#include <new>
template <typename T>
class ClassObject : public IClassFactory {
========== (ClassObject.hpp) ==========
テンプレートは通常のクラス定義の class の前に、
template 節を付け加えることで定義することができる。
template キーワードの後には山括弧に囲んだ引数がくる。
今回は「型」をテンプレートの引数とするので、
「typename T」となる。T が仮引数の名前であり、
typename が仮引数の型で「型を表す型」ということになる。
========== (ClassObject.hpp) ==========
// インタフェース
public:
// IUnknown のメソッド
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [out] */ void** ppvObject) {
// ppvObject が NULL なら例外
if (ppvObject == NULL) return E_POINTER;
// ppvObject は out なので、
// ポインタが指す元の値は無視できる
*ppvObject = NULL;
if (IsEqualIID(riid, IID_IClassFactory)) {
*ppvObject = static_cast<IClassFactory*>(this);
} else if (IsEqualIID(riid, IID_IUnknown)) {
*ppvObject = static_cast<IUnknown*>(this);
} else {
return E_NOINTERFACE;
}
// ポインタを作成した場合カウンタを増加
static_cast<IUnknown*>(*ppvObject)->AddRef();
return S_OK;
}
virtual ULONG STDMETHODCALLTYPE AddRef() {
// サーバをロック
++g_serverLockCount;
// インスタンスの参照カウンタは使わない
return 1;
}
virtual ULONG STDMETHODCALLTYPE Release() {
// サーバをアンロック
--g_serverLockCount;
// インスタンスの参照カウンタは使わない
return 1;
}
========== (ClassObject.hpp) ==========
続いて、IClassObject の継承している IUnknown の実装だ。
テンプレートの場合、メンバ関数(メソッド)の実体も、
クラス定義のなかに全て定義してしまうのが一般的である。
IUnknown の実装は一般的な内容だけなので、
HardLinkIconIDClass の実装をコピーするだけで済む。
========== (ClassObject.hpp) ==========
// IClassFactory のメソッド
virtual HRESULT STDMETHODCALLTYPE CreateInstance(
/* [in] */ IUnknown* pUnkOuter,
/* [in] */ REFIID riid,
/* [out] */ void** ppvObject) {
// ppvObject が NULL なら例外
if (ppvObject == NULL) return E_POINTER;
// COM 集約はサポートしない
if (pUnkOuter != NULL) return CLASS_E_NOAGGREGATION;
// ppvObject は out なので、
// ポインタが指す元の値は無視できる
*ppvObject = NULL;
// インスタンスを作成
T* instance = new (std::nothrow) T();
if (instance == NULL) return E_OUTOFMEMORY;
// 参照を追加
instance->AddRef();
// インタフェースを要求
HRESULT result = instance->QueryInterface(riid, ppvObject);
// 参照を解放
instance->Release();
return result;
}
virtual HRESULT STDMETHODCALLTYPE LockServer(
/* [in] */ BOOL fLock) {
if (fLock) {
++g_serverLockCount;
} else {
--g_serverLockCount;
}
return S_OK;
}
========== (ClassObject.hpp) ==========
そして IClassFactory の実装だ。
CreateInstance 内で生成するインスタンスの型として、
テンプレートパラメータの T を使って定義している
T* instance = new (std::nothrow) T();
テンプレートクラスの定義の場合、
この時点ではクラスとして実体化されないため、
テンプレートの引数である何かしらの型を表す T を使って、
コードを書くことができるのである。
この時点で T が何かは分かっている必要はないのだ。
========== (ClassObject.hpp) ==========
// コンストラクタ・デストラクタ
public:
ClassObject() {}
virtual ~ClassObject() {}
// 複製の禁止
private:
ClassObject(const ClassObject&);
ClassObject& operator=(const ClassObject&);
};
#endif // !classobject_hpp_included
========== end of ClassObject.hpp ==========
最後に、コンストラクタ・デストラクタと、
複製を禁止するイディオムを書いて完了となる。
これで、このヘッダファイルを取り込んだソースでは、
このテンプレートクラスの T に対して特定の型を当てはめ、
テンプレートを元にした具体的なクラスを使うことができる。