2008-01-15

もっとC++で遊ぶことにした

 前回、C++でメモリにやさしい動的多様性を実現しようとしたが、アラインメントの問題を指摘された。  そこで今回は、アラインメントも考慮したコードを書いてみた。
class BulletHolder { private : float x, y ; public : virtual void update() = 0 ; virtual BulletHolder * construct_copy(void * ptr) const = 0 ; virtual ~BulletHolder() { } } ; // BulletHolder *型のあとに構築されるオブジェクトの // アラインメントを合わせるために必要なパディング値を返す template < typename T > struct get_offset : boost::mpl::eval_if_c< sizeof(BulletHolder *) < boost::alignment_of<T>::value , boost::mpl::int_< boost::alignment_of<T>::value - sizeof(BulletHolder *) > , boost::mpl::int_< 0 > > { } ; #define BULLET_MAKE_CONSTRUCT_COPY_PP(TYPE) \ virtual BulletHolder * construct_copy(void * ptr) const \ { return new( static_cast<void *>(static_cast<char *>(ptr) + get_offset<TYPE>::value) ) TYPE(*this) ; } class Aligned_4_Bullet : public BulletHolder { private : boost::aligned_storage<4, 4>::type padding ; // 4バイトアライン public : BULLET_MAKE_CONSTRUCT_COPY_PP(Aligned_4_Bullet) virtual void update() { std::cout << "4 byte aligned " << std::endl ; } virtual ~Aligned_4_Bullet() { } } ; class Aligned_8_Bullet : public BulletHolder { private : boost::aligned_storage<8, 8>::type padding ; // 8バイトアライン public : BULLET_MAKE_CONSTRUCT_COPY_PP(Aligned_8_Bullet) virtual void update() { std::cout << "8 byte aligned" << std::endl ; } virtual ~Aligned_8_Bullet() { } } ; #undef BULLET_MAKE_CONSTRUCT_COPY_PP class Bullet { private : BulletHolder * ptr ; static size_t const size = 32 ; char buf[size] ; //BulletHolderを継承するすべてのクラスが収まるサイズ private : template < typename T > void * getAlignedptr() { return static_cast< void * >( &buf[ get_offset<T>::value ] ) ; } void * getbufptr() { return static_cast< void * >( &buf[ 0 ] ) ; } void safe_destruct() { if ( ptr != 0 ) ptr->~BulletHolder() ; } public : Bullet() : ptr(0) { } template < typename T > Bullet ( T const & x ) { BOOST_STATIC_ASSERT(( boost::is_base_of<BulletHolder, T>::value )) ; BOOST_STATIC_ASSERT(( sizeof(T) + get_offset<T>::value <= size )) ; ptr = new( getAlignedptr<T>() ) T(x) ; } template < typename T > Bullet & operator = ( T const & x ) { BOOST_STATIC_ASSERT(( boost::is_base_of<BulletHolder, T>::value )) ; BOOST_STATIC_ASSERT(( sizeof(T) + get_offset<T>::value <= size )) ; safe_destruct() ; ptr = new( getAlignedptr<T>() ) T(x) ; } Bullet( Bullet const & b ) { if (b.get() != 0) ptr = b.get()->construct_copy( getbufptr() ) ; } Bullet & operator = ( Bullet const & b ) { if ( this != &b && b.get() != 0) { safe_destruct() ; ptr = b.get()->construct_copy( getbufptr() ) ; } return *this ; } BulletHolder * get() const { return ptr ; } BulletHolder * operator *() { return get() ; } BulletHolder * operator ->() { return get() ; } ~Bullet() { safe_destruct() ; } // デバッグ用 void debug() { printf("&buf[0] == %p ptr = %p\n", getbufptr(), ptr ) ; } } ; int main() { Aligned_4_Bullet a4 ; Aligned_8_Bullet a8 ; Bullet b4(a4) ; Bullet b8(a8) ; b4.debug() ; b8.debug() ; }
 重要な部分は、get_offsetメタ関数だ。必要なパディング数を返してくれる。しかもすばらしいことに、このコードを利用する人は、これらの実装を一切気にしなくてもよいということだ。弾を作るにしても、 BulletHolderを継承して、BULLET_MAKE_CONSTRUCT_COPY_PPという、おまじないマクロを使っておけばよい。  上記のコードでは、たとえばポインタのサイズが4バイトだったとして、もし、3バイトアラインメントなどという変態アラインメントがあった場合は、問題なのだが、そもそもpower of 2以外のアラインメントなどありえるのだろうか。  ためしに、Boostのalignment_storageで、3バイトや5バイトのアラインメントを試してみたが、STATIC_ASSERTに引っかかる。Boostがダメと言っているのだから、たぶんそんな変態アラインメントのハードウェアは、存在しないのだろう。  しかし、つくづくテンプレートメタプログラミングは面白いと思う。get_offsetのようなコードを書くと、まさにコンパイラでメタプログラミングしているんだという実感が沸いてくる。

No comments: