immutable な string クラスを作りたい(1)

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ではアライメントによらず動くみたいですが、それでも気持ち悪いことに変わりはありません。


そこで、どうやればアライメントの問題を回避できるか考えた結果、

http://ideone.com/v0Q47Yie

#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);

のような感じで書けばいいなー、と思い至ったのですが、

実は

2005-11-02 - Cry’s Diary

で解説されているように、そんなこと考えずに

char* pv = new char[ sizeof(hoge) + len ];
hoge* p = ::new(pv) hoge();

と書けば、アライメントも含め、きちんと上手くいくようです。


いやまぁ、だから何、と言われるとアレなんですが。難しく考えすぎるなよ、ってことで一つ。

次回は参照カウントを仕込むところから始めたいと思います。



追記:
可変長配列メンバ - かそくそうち
も参考になりそうです。というか未だ読んでないので、後で読む。