BREW vector(4)

昨日の
やり方だと、容量が増えてしまうので、別の方法を考えてみます。


今度は、型に依存する演算子を、全て仮想関数にし、それを派生させてvectorを生成してみます。
こうすれば、型に依存する処理は全てそこで吸収することが可能になります。
こんな感じになります。

class vector_proxy{
protected:
    // コピーコンストラク
    virtual void _copy_constructor( void** _P , const void*& _X ) = 0;
    // デストラク
    virtual void _destroy( void** _P ) = 0;
    
    ...
    
    void** _First;
    void** _Last;
    void** _End;
};

_Firstや_Lastをダブルポインタにしているのは、VC6はvoid&が使えないからです。なんで使えないのよ(;´Д`)
仕方ないので、void*の参照を取る形で使用することにします。


で、これを扱うためのヘルパ関数を定義します。

class vector_helper{
    typedef void*           _Ty;
    typedef vector_proxy    _Myt;
    
    typedef size_t          size_type;
    typedef _Ty*            iterator;
    
#define iter_plus( v , n )  ((iterator)((byte*)(v) + (n)))
    
    static void constructor( _Myt& v , size_type n ,
                                size_type _N , const _Ty& _V ){
        v._First = _allocate( n , _N );
        _Ufill( v , n , v._First , _N , _V );
        v._Last = iter_plus( v._First , n * _N );
        v._End = v._Last;
    }
    static void _Ufill( _Myt& v , size_type n ,
                        iterator _F , size_type _N , const _Ty &_X ){
        for( ; 0 < _N ; --_N , _F = iter_plus( _F , n ) ){
            // 型に依存せずにコピーコンストラクタを呼び出す
            v._copy_constructor( _F , *(const void**&)_X );
        }
    }
    
    ...
    
};

例によってコンストラクタと_Ufillの定義です。
thisと型の大きさを、それぞれ第一引数と第二引数に渡し、残りのパラメータをその後ろに付けます。
これでどんな大きさの型でも自由に扱えます。
そして、型に依存する命令は、仮想関数にしているので、コピーコンストラクタを呼び出すことも可能になっているので、_Ufillも簡単に実装することが出来ます。


で、vector_proxyを継承して、vectorクラスを実装します。

template< class _Ty >
class vector{
protected:
    virtual void _copy_constructor( void** _P , const void*& _X ){
        new( (void*)_P ) _Ty( *(_Ty*)&_X );
    }
    virtual void _destroy( void** _P ){
        ((_Ty*)_P)->~_Ty();
    }
    
    ...
    
    explicit vector( size_type _N , const _Ty& _V = _Ty() ){
        vector_helper::constructor(
            *this , sizeof( _Ty ) , _N , *(void* const*)&_V );
    }
};

出来たヽ(´ー`)ノ
なんかおかしなキャストばっかりしていますが、上位クラスがvoid*なので仕方ないんです(;´Д`)


まあとりあえず、これでvectorの実装は、1つの関数に1命令だけで済ますことが可能になりました。
仮想関数を使用しているので、速度が気になるところです。
仮想関数を呼び出すためには、

  1. 仮想関数テーブルへのポインタを取り出す
  2. そのポインタの指す位置に、関数ごとに固有なオフセット値を足す
  3. そのポインタの指している値は、実際に呼び出したい関数へのポインタなので、それを取り出す
  4. 取り出したアドレスにジャンプ

という手順になります(多分^^;


アセンブラで書くと、

LDR     r0,[vptr]       ; 仮想テーブルの中身を取得
ADD     r0,r0,#4        ; オフセット値を加算
BX      [r0]            ; 関数呼び出し

こんな感じでしょうか。
普通の関数であれば、

BX      func            ; 関数呼び出し

こうやって呼び出すと思います。
つまり、(インライン展開されないのであれば)速度的にはLDR命令とADD命令一回分しか違わないわけで、少々仮想関数を使ったところで、大した違いは無いと思います。
……実測してないので断定は危険ですが(;´Д`)


まあ、速度面は解決したということにしておいて、次は、実際どれぐらい容量が減るのか試してみます。