BREW で Java 風のクラスを作る(2)


前回のプログラムの問題点は、キャストするたびに QueryInterface() を呼び出さなければならず、Java の文法とはかけ離れた処理を行わないといけなかったことです。
これを自然な書き方にするためには、内部で QueryInterface() を呼び出す必要があるため、キャストをするときに独自の処理を行わなければいけません。
独自の処理を行うということは、

IObject* pObj = (IObject*)pHoge;

のような、ポインタをキャストするのでは不可能なので、独自のキャスト処理を行うためのクラスを作ってやる必要があります。

#include <cstdio>
#include <windows.h>

// ここら辺はクラス名が変わっただけで
// あとはほとんど昨日と同じ

enum ID
{
    IID_Object,
    IID_Hoge,
    IID_HogeHoge,
    CLSID_Hoge,
};

class IObjectImpl
{
public:
    virtual HRESULT QueryInterface(ID id, void** pp) = 0;
};

class IHogeImpl : public IObjectImpl
{
public:
    virtual void Func() = 0;
};

class IHogeHogeImpl : public IObjectImpl
{
public:
    virtual void Func2() = 0;
};

class CHogeImpl : public IHogeImpl, public IHogeHogeImpl
{
public:
    virtual HRESULT QueryInterface(ID name, void** pp)
    {
        if (pp == NULL)
        {
            return E_POINTER;
        }
        switch (name)
        {
            case IID_Object:
                // IHoge 側の IObject を取得する
                *pp = (void*)static_cast<IObjectImpl*>(static_cast<IHogeImpl*>(this));
                break;
            case IID_Hoge:
                *pp = (void*)static_cast<IHogeImpl*>(this);
                break;
            case IID_HogeHoge:
                *pp = (void*)static_cast<IHogeHogeImpl*>(this);
                break;
            case CLSID_Hoge:
                *pp = (void*)static_cast<CHogeImpl*>(this);
                break;
            default:
                *pp = NULL;
                return E_NOINTERFACE;
        }
        return S_OK;
    }
    virtual void Func() { std::printf("call Func()"); }
    virtual void Func2() { std::printf("call Func2()"); }
};



// ここからが本題。

class IObject
{
public:
    IObject() : p(NULL) { }
    template<class T>
    IObject(const T& obj)
    {
        obj.QueryInterface(IID_Object, &p);
    }
    template<class T>
    IObject(ID id, const T& obj)
    {
        obj.QueryInterface(id, &p);
    }
    template<class T>
    IObject& operator=(const T& obj)
    {
        obj.QueryInterface(IID_Object, &p);
        return *this;
    }
    HRESULT QueryInterface(ID id, void** pp) const
    {
        return reinterpret_cast<IObjectImpl*>(p)->QueryInterface(id, pp);
    }
protected:
    void* p;
};

class IHoge : virtual public IObject
{
public:
    IHoge() { }
    template<class T>
    IHoge(const T& obj) : IObject(IID_Hoge, obj) { }
    template<class T>
    IHoge& operator=(const T& obj)
    {
        obj.QueryInterface(IID_Hoge, &p);
        return *this;
    }
    void Func() { return Get()->Func(); }
private:
    // 同じ値を返すだけでいいはず
    IHogeImpl* Get() { return reinterpret_cast<IHogeImpl*>(p); }
};

class IHogeHoge : virtual public IObject
{
public:
    IHogeHoge() { }
    template<class T>
    IHogeHoge(const T& obj) : IObject(IID_HogeHoge, obj) { }
    template<class T>
    IHogeHoge& operator=(const T& obj)
    {
        obj.QueryInterface(IID_HogeHoge, &p);
        return *this;
    }
    void Func2() { return Get()->Func2(); }
private:
    // 同じ値を返すだけでいいはず
    IHogeHogeImpl* Get() { return reinterpret_cast<IHogeHogeImpl*>(p); }
};

class CHoge : virtual public IHoge, virtual public IHogeHoge
{
public:
    CHoge() { }
    // 仮想継承は上位クラスを直接初期化することが出来る。
    template<class T>
    CHoge(const T& obj) : IObject(CLSID_Hoge, obj) { }
    template<class T>
    CHoge& operator=(const T& obj)
    {
        obj.QueryInterface(CLSID_Hoge, &p);
        return *this;
    }
    void Func() { return Get()->Func(); }
    void Func2() { return Get()->Func2(); }

    static CHoge New()
    {
        CHoge hoge;
        hoge.p = reinterpret_cast<void*>(new CHogeImpl());
        return hoge;
    }
private:
    // 同じ値を返すだけでいいはず
    CHogeImpl* Get() { return reinterpret_cast<CHogeImpl*>(p); }
};

void main()
{
    CHoge hogeInst = CHoge::New();
    IHoge hoge = (IHoge)hogeInst;
    IObject object = (IObject)hoge;
    IHogeHoge hogeHoge = (IHogeHoge)object;

    hogeHoge.Func2();
}

最初の方は、クラス名が変わっただけであとはほとんど前回と同じ内容になっているので、説明は省きます。
IObject 以降のクラスが、今回の焦点になります。


まず、ポインタではないオブジェクト同士のキャストを行った場合、例えば

IObject obj = (IObject)hoge;

こうした場合、値のコピーを行うために IObject::IObject(const Hoge& hoge) を呼び出そうとします。それが無かった場合は上位クラスで似たようなものが無いか探します。
で、もしコピーするためのメソッドが存在しない場合、同じクラスであればメモリの内容が直接コピーされ、そうじゃない場合はエラーになります。


そのことをふまえてみてみると、IObject 以降の各クラスはコピーコンストラクタと代入演算子が定義されていて、それはつまりキャストや代入を行った場合には必ず QueryInterface() を使用しているということです。
なので、IObject::p にはキャストを行っても正しいオブジェクトへのポインタが入っていることになります。
こうすることによって main() 関数内のようにキャストを行うことが出来るようになります。


しかも Java のように、ドット演算子でアクセスすることが可能になっているので、ほんとに Java っぽく扱えるようになり、一石二鳥です。
ただ、これは new をして delete をしていないのでリークします。
そもそも JavaGC が付いているので、Java っぽく書くためには GC を実装するか、もしくは COM の AddRef(), Release() を真似るかスマートポインタを使う必要があるでしょう。
次回はそれをやってみます。