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命令だけで済ますことが可能になりました。
仮想関数を使用しているので、速度が気になるところです。
仮想関数を呼び出すためには、
- 仮想関数テーブルへのポインタを取り出す
- そのポインタの指す位置に、関数ごとに固有なオフセット値を足す
- そのポインタの指している値は、実際に呼び出したい関数へのポインタなので、それを取り出す
- 取り出したアドレスにジャンプ
という手順になります(多分^^;
アセンブラで書くと、
LDR r0,[vptr] ; 仮想テーブルの中身を取得 ADD r0,r0,#4 ; オフセット値を加算 BX [r0] ; 関数呼び出し
こんな感じでしょうか。
普通の関数であれば、
BX func ; 関数呼び出し
こうやって呼び出すと思います。
つまり、(インライン展開されないのであれば)速度的にはLDR命令とADD命令一回分しか違わないわけで、少々仮想関数を使ったところで、大した違いは無いと思います。
……実測してないので断定は危険ですが(;´Д`)
まあ、速度面は解決したということにしておいて、次は、実際どれぐらい容量が減るのか試してみます。