BREW Smart Pointer(6)

補足。別名蛇足。


全てのスマートポインタクラスは、その上位クラスである BrewRefObject へ暗黙にキャストすることが可能だ。
しかし、それを正しい型に戻すためにはどうすれば良いだろうか。
まず考えられる方法が、オブジェクトと一緒に型固有の ID を格納する方法。

vector< BrewRefObject > objs;
vector< int > types;
objs.push_back( BrewSmartPtr< Hoge >( new Hoge() ) );
types.push_back( 0 );
objs.push_back( BrewSmartPtr< Hogehoge >( new Hogehoge() ) );
types.push_back( 1 );
objs.push_back( BrewSmartPtr< Hoge >( new Hoge() ) );
types.push_back( 0 );

for( int i = 0 ; i < objs.size() ; i++ ){
    BrewRefObject obj = objs[ i ];
    switch( types[ i ] ){
    case 0:
        {
            BrewSmartPtr< Hoge > hoge = obj;
            ....
        }
        break;
    case 1:
        {
            BrewSmartPtr< Hogehoge > hogehoge = obj;
            ....
        }
        break;
    }
}

こうすれば、正常な型に戻してから処理を行うことが出来る。
ただ、この方法は、元の型の ID を、手動で入れている。今回は2つだけなのだけれども、実際は100種類ぐらいの BrewRefObject を格納する可能性もある。それぞれの ID を覚えようとすることは無謀で、そして無駄だ。


これを回避する手段として、BrewRefObject と int を構造体やクラスに詰めて、スマートポインタを使うときには一緒に使用するという方法もあるのだけれど、そうするとコピーするときに余分なコストがかかるし、全てのクラスを wrap するのはかなり面倒だ。



そこで、スマートポインタを拡張することを考えてみる。目的は、キャストを繰り返しても型固有の ID が正常に取得出来るようにすること。こうすることによって、型情報を外部に持たせる手間を省くのだ。


まず型固有 ID を取得する方法なのだけれども、テンプレートは型毎に新しくクラスを生成するので、この値を直接取ればいい。

template< class T >
class BrewSmartPtr{
public:
    ....
    static uint32 TypeID(){
        return (uint32)&TypeFunc;
    }
private:
    // この関数へのポインタを、型固有の ID として使用する
    static void TypeFunc(){}
};

これで型固有の ID を取得することが可能だ。しかし、これだけでは上手く動作しない。
次のように書いた場合、

BrewSmartPtr< Hoge > hoge( new Hoge() );
BrewSmartPtr< Hoge2 > hoge2 = hoge;
if( hoge2.TypeID() == BrewSmartPtr< Hoge >::TypeID() ){
    ....
}

最後の if 文の中は実行されなければならない。しかし、hoge2 の型は BrewSmartPtr< Hoge2 > であり、TypeID() を実行して返されるのは BrewSmartPtr< Hoge2 >::TypeID() なのだ。if 文は実行されない。そしてこれは意図した動作ではない。
つまり、最初に生成するときに使用した型の固有 ID を保持しなければならないのだ。
ただ、それを BrewSmartPtr のメンバとして持つと、コピーするときに余分なコストがかかる。固有 ID というのは本質的には const な値であるので、これは冗長だ。


そこで、Deleter が本来の型を持っていることを利用して、BrewRefCountDeleterBase に対して型固有の ID を付けることを考える。

// 参照カウントとオブジェクト解体を行うクラス
class BrewRefCountDeleterBase{
public:
    BrewRefCountDeleterBase() : _count( 1 ){};
    virtual ~BrewRefCountDeleterBase(){}
    
    // オブジェクトを解体する
    virtual void Delete( void* p ) const = 0;
    // 型固有の ID を返す
    virtual uint32 GetTypeID() const = 0;
    // 参照数を増やす
    uint32 inc(){ return ++_count; }
    // 参照数を減らす
    uint32 dec(){ return --_count; }
private:
    uint32      _count;
};

// delete で解体する Deleter
template< class T >
class BrewRefCountDeleter : public BrewRefCountDeleterBase{
public:
    virtual void Delete( void* p ) const{
        delete static_cast< T* >( p );
    }
    virtual uint32 GetTypeID() const{
        return TypeID();
    }
    static uint32 TypeID(){
        return (uint32)&TypeFunc;
    }
private:
    static void TypeFunc(){};
};

// delete で解体する Deleter
template< class T >
class BrewRefCountArrayDeleter : public BrewRefCountDeleterBase{
public:
    virtual void Delete( void* p ) const{
        delete static_cast< T* >( p );
    }
    virtual uint32 GetTypeID() const{
        return TypeID();
    }
    static uint32 TypeID(){
        return (uint32)&TypeFunc;
    }
private:
    static void TypeFunc(){};
};

スマートポインタクラスはこの関数を使って、型固有の ID を返してやる。

// 参照カウントを行うためのクラス
class BrewRefObject{
public:
    ....
    // 本来の、型固有の ID を返す
    uint32 GetTypeID() const{
        return _ref->GetTypeID();
    }
    ....
};

// スマートポインタクラス
template< class T >
class BrewSmartPtr : public BrewRefObject{
public:
    ....
    // 型固有の ID を返す
    static uint32 TypeID(){
        return BrewRefCountDeleter< T >::TypeID();
    }
    ....
};

こうすれば、本来の型の固有 ID を取得し、判定することが可能になる。

BrewSmartPtr< Hoge > hoge( new Hoge() );
BrewSmartPtr< Hoge2 > hoge2 = hoge;
if( hoge2.GetTypeID() == BrewSmartPtr< Hoge >::TypeID() ){
    // 実行される
    ....
}

ただ、これによって元の型固有の ID を確認することが出来るのようになったのだけれども、それでも C++ の type_info と比べるとあまりにもショボ過ぎる。気休め程度にしかならない。が、無いよりはマシだろう。