2006 年 12 月 3 日 23 時 58 分

ClassObject テンプレートクラス


このアーカイブは同期化されません。 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 に対して特定の型を当てはめ、
テンプレートを元にした具体的なクラスを使うことができる。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.