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


Java っぽく作るためには、メモリがどこからも参照されなくなったら自動的に解放されるようにする必要があります。
方法としては、

  1. GC を実装する。
  2. AddRef(), Release() を使用する。
  3. スマートポインタを使用する。

ぐらいだと思われます。
1番目は規模が大きすぎるので除外します。
2番目と3番目は参照カウントによる管理だから同じと思うかもしれませんが、2番目の方法は実体の中に参照カウントの値を持っていて、3番目の方法は実体の外に参照カウントの値を持っているので、少しだけ違います。
外部から見ればほとんど違いは無いですが、使用するメモリの量が2番目の方が少ないので、BREW の場合はそちらの方がいいかもしれません。
ただ、各実装毎に AddRef(), Release() を定義するという手間はあります。これはマクロや template<> である程度解決出来ます。


以下は AddRef(), Release() を用いた、自動的に解放されるような実装。

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

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

class IObjectImpl
{
public:
    virtual int AddRef() = 0;
    virtual int Release() = 0;
    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
{
private:
    int count;
public:
    CHogeImpl() : count(0) { }

    virtual int AddRef()
    {
        std::printf("AddRef(), count = %d\n", count + 1);
        return ++count;
    }
    virtual int Release()
    {
        std::printf("Release(), count = %d\n", count - 1);
        if (--count == 0)
        {
            delete this;
            return 0;
        }
        return count;
    }

    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;
        }
        reinterpret_cast<IObjectImpl*>(*pp)->AddRef();
        return S_OK;
    }

    virtual void Func() { std::printf("call Func()\n"); }
    virtual void Func2() { std::printf("call Func2()\n"); }
};



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)
    {
        IObjectImpl* p_ = reinterpret_cast<IObjectImpl*>(p);
        obj.QueryInterface(IID_Object, &p);
        if (p_ != NULL)
        {
            p_->Release();
        }
        return *this;
    }
    ~IObject()
    {
        reinterpret_cast<IObjectImpl*>(p)->Release();
    }
    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)
    {
        IObjectImpl* p_ = reinterpret_cast<IObjectImpl*>(p);
        obj.QueryInterface(IID_Hoge, &p);
        if (p_ != NULL)
        {
            p_->Release();
        }
        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)
    {
        IObjectImpl* p_ = reinterpret_cast<IObjectImpl*>(p);
        obj.QueryInterface(IID_HogeHoge, &p);
        if (p_ != NULL)
        {
            p_->Release();
        }
        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)
    {
        IObjectImpl* p_ = reinterpret_cast<IObjectImpl*>(p);
        obj.QueryInterface(CLSID_Hoge, &p);
        if (p_ != NULL)
        {
            p_->Release();
        }
        return *this;
    }

    void Func() { return Get()->Func(); }
    void Func2() { return Get()->Func2(); }

    static CHoge New()
    {
        CHoge hoge;
        hoge.p = reinterpret_cast<void*>(new CHogeImpl());
        reinterpret_cast<IObjectImpl*>(hoge.p)->AddRef();
        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();
}
出力:
AddRef(), count = 1
AddRef(), count = 2
Release(), count = 1
AddRef(), count = 2
AddRef(), count = 3
AddRef(), count = 4
call Func2()
Release(), count = 3
Release(), count = 2
Release(), count = 1
Release(), count = 0

ちゃんとカウントが 0 になって解放されています。
途中で Release() が呼ばれている理由は、CHoge::New の中に CHoge 型のローカル変数があり、return hoge; によって AddRef() された後に Release() されているからです。


次回はマクロとかいろいろ使ってもっと単純に書けるようにしてみます。