C++標準のstd::stringは非常に優秀な文字列ライブラリですが、少しばかりオーバースペックに感じる時があります。
現行の std::strinig は copy-on-write 方式を採用出来るように設計されているのですが、そもそも文字列を書き換えたい場合って少ないので、 copy なんて別に必要ないなーという感じ。
そこで、数回に分けて「僕の考えた最強 string クラス」を作ってみようかなあ、と思い立ったので、適当にネタにしてみようと思います。
考え方としては、参照カウント方式を使います。
struct immutable_string { /* インターフェイス */ private: typedef 〜 impl; boost::intrusive_ptr<impl> p; };
こんな感じで作れば、コピーコストは参照カウントのインクリメントだけと、非常に安価に行えていい感じ。
問題は impl ですが、安直に
struct immutable_string_impl { int size; char* buf; immutable_string_impl( const char* str ) : size( std::strlen(str) ), buf( new char[size] ) { std::strcpy( buf, str ); } ~immutable_string_impl() throw() { delete [] buf; } };
とかやるのも悪くないのですが、それだと intrusive_ptr に格納するのにメモリを確保するのと合わせ、都合2回メモリ確保を行うことになり、少し不満です。
この解決策としては、
struct immutable_string_impl { int size; char buf[1]; };
とやって、 immutable_string_impl のメモリ確保の際に buf の分の領域も余分に確保して、とやると、一回のメモリ確保で済むようになります。
char* pv = new char[ sizeof(immutable_string_impl) + len ]; immutable_string_impl* p = ::new(pv) immutable_string_impl(); // 作業 p->~immutable_string_impl(); delete [] pv;
この方法は良さそうなのですが、ひとつだけ心配な点が。
それは、 new char [] したメモリ領域のアライメントってどうなってるか、という問題です。
もしアライメントが調節されてないアドレスが帰ってきたら、この黒魔術は動かないかもしれないのです。実はx86ではアライメントによらず動くみたいですが、それでも気持ち悪いことに変わりはありません。
そこで、どうやればアライメントの問題を回避できるか考えた結果、
#include <boost/type_traits/type_with_alignment.hpp> // アライメントの確保されたメモリ領域を得る template<std::size_t Align> void* aligned_malloc( std::size_t size ) { typedef typename boost::type_with_alignment<Align>::type T; std::size_t const n = ( size + sizeof(T) - 1 ) / sizeof(T); return new T[n]; } // aligned_malloc で得た領域を解放する template<std::size_t Align> void aligned_free( const void* p ) { typedef typename boost::type_with_alignment<Align>::type T; delete [] static_cast<const T*>(p); } #include <boost/type_traits/alignment_of.hpp> #include <memory> struct hoge { std::size_t size; char buf[1]; }; std::size_t const align = boost::alignment_of<hoge>::value; void* const pv = aligned_malloc<align>( sizeof(hoge) + len ); hoge* const p = ::new(pv) hoge(); /* 処理 */ p->~hoge(); aligned_free<boost::alignment_of<hoge>::value>(p);
のような感じで書けばいいなー、と思い至ったのですが、
実は
で解説されているように、そんなこと考えずに
char* pv = new char[ sizeof(hoge) + len ]; hoge* p = ::new(pv) hoge();
と書けば、アライメントも含め、きちんと上手くいくようです。
いやまぁ、だから何、と言われるとアレなんですが。難しく考えすぎるなよ、ってことで一つ。
次回は参照カウントを仕込むところから始めたいと思います。
追記:
可変長配列メンバ - かそくそうち
も参考になりそうです。というか未だ読んでないので、後で読む。