前回 の続き。
用意した可変長バッファに、参照カウントを仕込みます。
しかし、単純に参照カウントを組み込むだけではつまらないので、一工夫。
参照カウントの部分を、それ以外の部分とは独立した部品として切り出してみます。
そうすれば、以降に参照カウントを使うクラスを作るときに楽できますし、可読性も上がります。
実を言うと既存の俺ライブラリにも、参照カウントを仕込むためのライブラリ 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 を実装してみます。