BREW で Java 風のクラスを作る

424 :デフォルトの名無しさん :2007/05/07(月) 22:37:54
Javaのインターフェースのような振る舞いをさせたい場合はどうしたらいいんだろう?


クロスキャストで質問なんだが。。。
Javaからの移植を今やってる。で以下の継承ツリーを持つクラスがある。


Object -> ClassA -> ClassB -> ClassC -> ClassD
                ↑      ↑
       Object -> ClassE     ↑
                       ↑
       Object -> ClassF -> ClassG

※)みぎにいくほどサブクラス。ClassE,ClassF,ClassGはJavaではインターフェース。
BREWでは純粋仮想関数だけを持つクラス


ちなみにClassBの宣言は
class ClassB : public ClassA, public virtual ClassE


ClassCの宣言も同様に
class ClassC : public ClassB, public virtual ClassG


でこのクラスに対して


ClassD* classD = new ClassD();
ClassC* classC = (ClassC*)classD;
ClassB* classB = (ClassB*)classC;
Object* obj = (Object*)classB;
とUPキャストしてObject型にする。んでそのあとに


ClassG* classG = (ClassG*)obj;
classG->hoge();
をやるとまったく違う関数が呼ばれてしまう。。。。どうしたらいいんだろ。。。。

http://pc11.2ch.net/test/read.cgi/tech/1166675852/424

ちょっと面白そうなので考えてみました。


↑のプログラムの問題点としては、Object* にキャストしたとき、その obj が ClassD* であったという情報が失われているということにあります。
ClassD* から ClassG* にキャストした場合は仮想テーブルのオフセットをずらして正しい ClassG* へのポインタを取得することが可能ですが、Object* から ClassG* にキャストした場合、Object* は ClassG* を取得するためにはオフセットをずらして返さなければならないということを知らないので、(void*)classG == (void*)obj になってしまって正しい ClassG* へのポインタが取得できません。


これを解決するためには、(↑の質問をした本人も >>427 で書いていますが)QueryInterface() みたいな機能を自前で実装することだと思います。

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

class IObject
{
public:
    // もう BREW の書き方忘れたので HRESULT で。
    virtual HRESULT QueryInterface(ID name, void** pp) = 0;
    virtual ~IObject() { }
};

class IHoge : public IObject
{
public:
    virtual void Func() = 0;
};

class IHogeHoge : public IObject
{
public:
    virtual void Func2() = 0;
};

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

単に要求された型に変換して返しているだけです。
COM と違うのは、インターフェースだけでなく、CLSID の要求に対しても型変換を行っているところです。
注意するところとしては、IID_Object が要求されたときに、IHoge 側の IObject を使用しているところでしょうか。


こんな感じで使用します。

CHoge* pHogeInst = new CHoge();
IHoge* pHoge;
if (pHogeInst->QueryInterface(IID_Hoge, &pHoge) != S_OK)
{
    // キャストらめえええええ
    throw bad_cast;
}
IObject* pObject;
if (pHoge->QueryInterface(IID_Object, &pObject) != S_OK)
{
    // キャストらめえええええ
    throw bad_cast;
}
// IHogeHoge* pHogeHoge = (IHogeHoge*)pObject;
// ↑こうやって書くのは不正。
IHogeHoge* pHogeHoge;
if (pObject->QueryInterface(IID_HogeHoge, &pHogeHoge) != S_OK)
{
    // バカバカ!○○○!
    throw bad_cast;
}

こうすることによって、キャスト可能かどうかが動的に判断出来るようになります。
ただし、毎回こうやって書くのはかなり厳しいので、もっと Java っぽく書けるようにしてしまいましょう(つづく)