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

前回 の続き。
用意した可変長バッファに、参照カウントを仕込みます。


しかし、単純に参照カウントを組み込むだけではつまらないので、一工夫。
参照カウントの部分を、それ以外の部分とは独立した部品として切り出してみます。
そうすれば、以降に参照カウントを使うクラスを作るときに楽できますし、可読性も上がります。
実を言うと既存の俺ライブラリにも、参照カウントを仕込むためのライブラリ reference_counter が存在します。
するのですが、こいつは delete 演算子でしか解放が行えません。
よって、メモリを char の配列で確保してから placement new を呼んでる今回は、残念ながら使うことが出来ません。
ですので今回は、解放処理に delete 演算子だけでなく、任意の deleter を指定できるようなライブラリ intrusive_hook を作ろうと思います。



というわけで、早速ソースコードを書いてみました:

#include <cassert>
#include <boost/compressed_pair.hpp>

namespace gintenlib
{
 namespace intrusive_hook_  // ADL 回避用名前空間
 {
  // 普通の delete 演算子を呼び出す deleter
  // <gintenlib/deleter.hpp> に既に存在するもの
  struct deleter
  {
    typedef void result_type;
    
    template<typename T>
    void operator()( T* p ) const
    {
      delete p;
    }
    
  };  // struct deleter
  
  // 本体
  template<typename Derived, typename Deleter = deleter>
  class intrusive_hook
  {
    typedef intrusive_hook this_type;
    
    typedef int counter_type;
    typedef Deleter  deleter;
    
    // メンバ
    typedef boost::compressed_pair<counter_type, deleter> pair_type;
    mutable pair_type pair_;
    
    // メンバアクセス
    counter_type& get_count() const { return pair_.first(); }
    deleter const& get_deleter() const { return pair_.second(); }
    
    // Derived からのメンバアクセス
    static counter_type& get_count( Derived const& x )
    {
      return static_cast<this_type const&>(x).get_count();
    }
    static deleter const& get_deleter( Derived const& x )
    {
      return static_cast<this_type const&>(x).get_deleter();
    }
    
    
    // カウントの増減
    // ADLによって呼び出される
    friend void intrusive_ptr_add_ref( Derived const* p )
    {
      using namespace std;
      assert( p != 0 );
      
      counter_type& count = get_count(*p);
      assert( count >= 0 );
      
      ++count;
      
    }
    friend void intrusive_ptr_release( Derived const* p )
    {
      using namespace std;
      assert( p != 0 );
      
      counter_type& count = get_count(*p);
      assert( count > 0 );
      
      if( --count == 0 )
      {
        // p の破棄処理の途中で deleter が削除されると困るのでコピーする
        deleter d = get_deleter(*p);
        // 削除本体
        d(p);
      }
    }
    
   protected:
    // デフォルトコンストラクタ
    intrusive_hook()
      : pair_( 0, deleter() ) {}
    // コピーコンストラクタ(コピーしない)
    intrusive_hook( const this_type& )
      : pair_( 0, deleter() ) {}
    // 削除子指定
    intrusive_hook( const Deleter& d )
      : pair_( 0, d ) {}
    // 初期カウント指定
    intrusive_hook( int initial_count )
      : pair_( initial_count, deleter() ) {}
    // 初期カウント指定&削除子指定
    intrusive_hook( int initial_count, const Deleter& d )
      : pair_( initial_count, d ) {}
    
    // operator=(何もしない)
    this_type& operator=( const this_type& )
    {
      return *this;
    }
    
  };  // intrusive_hook<Derived, Deleter>
  
 }  // namespace intrusive_hook_
 
 // 単純に gintenlib:: で使えるようにする
 using namespace intrusive_hook_;
 
} // namespace gintenlib

使い方は reference_counter と同様に、

struct hoge
  : public gintenlib::intrusive_hook<hoge>
{
  // 中身
};

こう書きます。デリータを指定したい場合はテンプレートの第二引数で指定します。


特に難しい点はないのですが、コピー時の動作が少し独特なので、説明します。
まず参照カウントはコピーされません。これは、少し考えれば分かると思います。
またデリータも同様に、コピーされません。この動作はかなり悩んだのですが、参照カウントとデリータはセットで管理するという原則を採ることにしました。
それに合わせて、代入動作も何も行わないよう設定してあります。
もしその動作が嫌ならば、その都度明示的にコンストラクタを呼び出す事で対処する感じで。
本来ならば noncopyable にするべきなのかもしれませんが、派生先で自動定義されたコンストラクタを使えるように、あえて noncopyable にはしていません。


さて、こうして作った intrusive_hook を用いて、昨日の immutable_string_impl を実装してみます。

// immutable_string_impl に対するデリータ
struct i_s_i_deleter
{
  typedef void result_type;
  
  template<typename T>
  void operator()( T* p ) const
  {
    // デストラクタをまず呼び出す
    p->~T();
    // 領域を解放する
    void const* const vp = p;
    delete [] static_cast<const char*>(vp);
  }
  
};  // i_s_i_deleter

#include <boost/intrusive_ptr.hpp>

// 本題
class immutable_string_impl
  : public gintenlib::intrusive_hook<immutable_string_impl, i_s_i_deleter>
{
  typedef immutable_string_impl this_type;
  
 public:
  std::size_t const buf_size; // 領域長は固定
  char buf[1];
  
  // オブジェクト製作
  static boost::intrusive_ptr<this_type> create( std::size_t size )
  {
    // 領域を確保し
    void* const vp = ::new char[ sizeof(this_type) + size ];
    // placement new でオブジェクトを構築する
    return boost::intrusive_ptr<this_type>( ::new (vp) immutable_string_impl(size) );
  }
  
 private:
  friend class i_s_i_deleter;
  
  // 外部からの構築禁止
  explicit immutable_string_impl( std::size_t size )  // never throws
    : buf_size( size ) {}
  // 破棄も禁止
  ~immutable_string_impl() {}
  
}; // immutable_string_impl

こんな感じ。これで、一回のメモリ確保で可変長バッファを確保でき、メモリ管理はスマートポインタに任せることが可能になりました。


次回はこれを元に immutable_string を実装してみます。