C++ の template は非常に便利なのですが、型毎にクラスが作られるため、バイナリサイズが増えてしまいます。
大体はそんなのは無視することが出来るのでしょうけど、本当に容量の厳しい組み込み系のアプリ(というかBREWアプリ)の場合、そうもいきません。
昔、template をガシガシ使った BREW のアプリを作ったのですが、大体バイナリサイズは 400KB ぐらいになりました。
その後 template を使わずに同じような規模のアプリを作ってみると、バイナリサイズは 200KB ぐらいでした。
単純計算で2倍の違いが出ています。
300KB までのアプリしか入らないという制限がある端末があったとすれば、template を使わずに作った 200KB のアプリは入っても、template を使って作った 400KB のアプリは入らないことになります。
そこで選択肢としては、
1.300KB 制限の端末を切り捨てる
2.template を使わずに書く
というのがあるわけですが、template を使わずに書くことによる作業効率の低下と、300KB 制限を切り捨てることによるシェアの低下を天秤に掛けることになります。
しかし、何とか template を使ってもバイナリサイズが小さくなるような方法は無いのでしょうか。
なぜバイナリサイズが大きくなるかというと、型毎にクラスが生成されてしまうからです。
同じような処理で、型だけ違うようなメソッドを幾つも作っているわけです。
言い換えれば、バイナリのコード密度が低いということです。
例えば vector の場合、処理全てを template にする必要はなく、最低限、型のサイズ・コピーコンストラクタ・デストラクタ・代入演算子(operator=)・比較演算子(operator==, operator<, operator>)があれば、あとは共通の処理で書くことが出来ます。
以下のようなコードになります。
class vector_proxy{
protected:
virtual void _copy_constructor( void** _P , const void*& _X ) = 0;
virtual void _destructor( void** _P ) = 0;
virtual void _object_equal( void*& lhs , const void*& rhs ) = 0;
virtual bool _object_eqeq( const void*& lhs , const void*& rhs ) = 0;
virtual bool _object_lt( const void*& lhs , const void*& rhs ) = 0;
void** _First;
void** _Last;
void** _End;
friend class vector_helper;
};
class vector_helper{
private:
typedef void* _Ty;
typedef vector_proxy _Myt;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef _Ty* _Tptr;
typedef const _Ty* _Ctptr;
typedef _Ty& reference;
typedef const _Ty& const_reference;
typedef _Ty value_type;
typedef _Tptr iterator;
typedef _Ctptr const_iterator;
#define iter_plus( p , n ) ((iterator)((byte*)(p) + (n)))
#define iter_minus( p , n ) ((iterator)((byte*)(p) - (n)))
#define c_iter_plus( p , n ) ((const_iterator)((byte*)(p) + (n)))
#define c_iter_minus( p , n ) ((const_iterator)((byte*)(p) - (n)))
public:
static void constructor( _Myt& v , size_type n , const _Myt& _X ){
v._First = _allocate( _size( _X ) );
v._Last = _Ucopy( v , n , _X._First , _X._Last , v._First );
v._End = v._Last;
}
static void destructor( _Myt& v , size_type n ){
_Destroy( v , n , v._First , v._Last );
_deallocate( v._First );
v._First = 0;
v._Last = 0;
v._End = 0;
}
...
private:
static void _Destroy( _Myt& v , size_type n , iterator _F , iterator _L ){
for( ; _F != _L ; _F = iter_plus( _F , n ) ){
v._destructor( _F );
}
}
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 );
}
}
static iterator _allocate( size_type n , size_type _N ){
return (iterator)operator new( n * _N );
}
static void _deallocate( iterator _F ){
operator delete( _F );
}
};
template< class _Ty >
class vector : protected vector_proxy{
public:
typedef vector< _Ty > _Myt;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef _Ty* _Tptr;
typedef const _Ty* _Ctptr;
typedef _Ty& reference;
typedef const _Ty& const_reference;
typedef _Ty value_type;
typedef _Tptr iterator;
typedef _Ctptr const_iterator;
vector( const _Myt& _X )
{ vector_helper::constructor( *this , _sizeof() , _X ); }
~vector()
{ vector_helper::destructor( *this , _sizeof() ); }
...
protected:
virtual void _copy_constructor( void** _P , const void*& _X )
{ new( (void*)_P ) _Ty( *(_Ty*)&_X ); }
virtual void _destructor( void** _P )
{ ((_Ty*)_P)->~_Ty(); }
virtual void _object_equal( void*& lhs , const void*& rhs )
{ (*(_Ty*)&lhs) = (*(const _Ty*)&rhs); }
virtual bool _object_eqeq( const void*& lhs , const void*& rhs )
{ return ((*(_Ty*)&lhs) == (*(_Ty*)&rhs)); }
virtual bool _object_lt( const void*& lhs , const void*& rhs )
{ return ((*(_Ty*)&lhs) < (*(_Ty*)&rhs)); }
size_type _sizeof()
{ return sizeof( _Ty ); }
};
長くてすんませんこれでも減らした方なんです(;´Д`)
注目すべきは vector クラスで、vector クラスのメソッド全てが、1行で定義されていることです。
本来の処理を、非template な vector_helper に委譲することによって、vector テンプレートが増えても容量がほとんど増えなくなります。
こうやって、最低限必要な部分だけ取りだして、それ以外の処理を全て非 template に委譲してやれば、コード密度が高くなり、容量をほとんど増やすことなく、template を使用することが出来るようになります。
厳しい容量の中でどうしても template が使いたい場合にどうぞー(そして誰もいなくn(ry