ユーザー独自の allocator を定義することは、比較的単純なプロセスです。標準 C++ ライブラリには、型と関数で構成される特定のインタフェースが記述されています。標準と準拠するアロケータは、これらのメンバー関数と型の構文要求に準拠している必要があります。また、標準 C++ ライブラリは、構文の一部をアロケータ型に指定しています。
標準 C++ ライブラリのアロケータインタフェースは、メンバーテンプレートに深く依存しています。このマニュアルの執筆時点では、コンパイラの多くが、メンバー関数テンプレートもメンバークラステンプレートもまだサポートしていません。このため、標準アロケータはまだ実装することができません。Rogue Wave の標準 C++ ライブラリの実装によって、選択不可のコンパイラ機能を要求せずに、標準インタフェースのほとんどの機能を提供する代替アロケータインタフェースが提供されます。このインタフェースは、標準インタフェースとは多少異なり、他のベンダーの標準 C++ ライブラリバージョンでは有効に機能しません。
アロケータを定義したり、コンテナを実装するときは、標準インタフェースとRogue Wave インタフェースを両方指定することを推奨します。これにより、アロケータをただちに使用できると共に、標準インタフェースがコンパイラに使用可能になり次第、標準の利点を活用することができます。
この章の以降の節では、標準 C++ ライブラリアロケータの要求、Rogue Wave の代替アロケータの要求、2 つのインタフェースを同じコードでどのようにサポートするかを指定する方法について説明します。
標準 C++ ライブラリアロケータ仕様に準拠する allocator は、以下のインタフェースを備えている必要があります。この例では、ユーザー独自のアロケータ名のプレースホルダとして my_allocator を使用します。
template <class T> class my_allocator { typedef implementation_defined size_type; typedef implementation_defined difference_type typedef implementation_defined pointer; typedef implementation_defined const_pointer; typedef implementation_defined reference; typedef implementation_defined const_reference; typedef implementation_defined value_type; template <class U> struct rebind { typedef allocator<U> other; };
このインタフェースでは、各ポインタ型は void* へ変換する必要があります。結果として得られる void* は、コンストラクタまたはデストラクタに、また適当な B、すなわち B::deallocate() 用の B<void>::pointer への変換に this 値として使用できなければなりません。
rebind メンバーを使用して、コンテナは、テンプレートパラメータとして提供されるアロケータ型から任意の型のアロケータを作成することができます。たとえば、list コンテナは、デフォルトで allocator<T> を作成しますが、list に list_node と T の割り当てが必要となる場合もあります。コンテナは、T へのアロケータから、この場合テンプレートパラメータ Allocator である list_node へのアロケータを以下のように構築することができます。
Allocator::rebind<list_node>::other list_node_allocator;
以下は、標準 C++ ライブラリの allocator が定義する必要のあるメンバー関数の記述です。
my_allocator(); template <class U> my_allocator(const my_allocator<U>&); template <class U> operator=(const my_allocator<U>&); ~my_allocator();
コンストラクタとデストラクタ
pointer address(reference r) const;
r のアドレスを pointer 型として返します。この関数と以下の関数は、参照をポインタに変換するために使用されます。
const_pointer address(const_reference r) const;
r のアドレスを const_pointer 型として返します。
pointer allocate(size_type n, const_pointer hint=0);
T の n 値に記憶を割り当てます。可能であれば、hint の値を使用して記憶の配置を最適化します。
void deallocate(pointer);
allocate の呼び出しによって獲得した記憶の割り当てを解除します。
size_type max_size();
allocate の呼び出しによって使用可能な最大記憶を返します。
void construct(pointer p, const T& val);
T のコンストラクタ呼び出しで u の値を使用して、型 T のオブジェクトを位置 p で作成します。結果は次のとおりです。
new((void*)p) T(u);
void destroy(pointer p);
p により呼び出される値にデストラクタを呼び出します。結果は次のとおりです。
((T*)p)->~T()
以下は、標準 C++ ライブラリの allocator が定義する必要のある非メンバー関数の記述です。
template <class T> my_allocator::pointer operator new(my_allocator::size_type, my_allocator&);
my_allocator::allocate を使用して、型 T の単一オブジェクトのスペースを割り当てます。結果は次のとおりです。
new((void*)x.template allocate<T>(1)) T;
template <class T, class U> bool operator==(const my_allocator<T>& a, const my_allocator<U>& b);
アロケータ b および a が安全に交換できる場合は、true を返します。安全に交換できるとは、a を使用して獲得された記憶の割り当てを b を使用して解除でき、その反対もまた可能であることを意味します。
template <class T, class U> bool operator!=(const my_allocator<T>& a, const my_allocator<U>& b);
!(a == b) を返します。
Rogue Wave では、クラステンプレートとメンバー関数テンプレートのどちらもサポートしないコンパイラに、代替アロケータインタフェースを提供します。
このインタフェースでは、クラス allocator_interface がすべての型と型付き関数を提供します。メモリーは、Allocator テンプレートパラメータによって提供されるクラスを使用して、未使用バイトとして割り振ります。 allocator_interface 内の関数は、ポインタ値を返す前に適宜キャストされます。複数の allocator_interface オブジェクトを単一の allocator に添付できるため、関連する型の数にかかわらず、1 つの allocator でコンテナのすべての記憶を割り当てることができます。実際の制約の 1 つに、ポインタと参照が型 T* および T& としてハードコード化されることがあります (標準インタフェースでは、これは implementation_defined となります)。コンパイラがメンバーテンプレートではなく部分特殊化をサポートする場合、これを使用してアロケータ型に allocator_interface を特殊化して、この制約に対処することができます。
代替インタフェースを基に allocator を実装するには、次のようにラベル付きクラス my_allocator を指定します。
// // 代替アロケータはインタフェースクラス // (allocator_interface) を使用して // 型を安定化させる // template <class T> class my_allocator { public: typedef implementation_defined size_type; typedef implementation_defined difference_type; typedef implementation_defined pointer; typedef implementation_defined const_pointer; typedef implementation_defined reference; typedef implementation_defined const_reference; typedef implementation_defined value_type; my_allocator(); ~my_allocator();
void * allocate (size_type n, void * = 0); void deallocate (void* p); size_type max_size (size_type size) const };
標準コンテナがクラスをどのように使用するかを示すため、allocator_interface クラスの完全実装リストも含めました。コンテナが代替インタフェースを使用する方法については、第 16 章で詳しく説明します。
template <class Allocator,class T> class allocator_interface { public: typedef Allocator allocator_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef T value_type; typedef typename Allocator::size_type size_type; typedef typename Allocator::difference_type difference_type;
protected: allocator_type* alloc_;
public: allocator_interface() : alloc_(0) { ; } allocator_interface(Allocator* a) : alloc_(a) { ; }
void alloc(Allocator* a) { alloc_ = a; }
pointer address (T& x) { return static_cast<pointer>(&x); }
size_type max_size () const { return alloc_->max_size(sizeof(T)); }
pointer allocate(size_type n, pointer = 0) { return static_cast<pointer>(alloc_->allocate(n*sizeof(T))); }
void deallocate(pointer p) { alloc_->deallocate(p); }
void construct(pointer p, const T& val) { new (p) T(val); }
void destroy(T* p) { ((T*)p)->~T(); }
};
class allocator_interface<my_allocator,void> { public: typedef void* pointer; typedef const void* const_pointer; };
// // アロケータ大域 // void * operator new(size_t N, my_allocator& a); inline void * operator new[](size_t N, my_allocator& a); inline bool operator==(const my_allocator&, const my_allocator&);
Rogue Wave では、標準 C++ ライブラリのアロケータインタフェースと、代替インタフェースの両方をサポートするコンテナを実装することを強く推奨します。両方のインタフェースがサポートされていれば、アロケータをすぐに使用でき、また標準アロケータインタフェースがコンパイラに使用可能になったときに標準の利点を活用することができます。
アロケータインタフェースの両バージョンを実装するには、コンテナに、標準インタフェースが使用可能かどうかを判断する機構が必要となります。Rogue Wave は、標準アロケータが使用可能かどうかを定義するためのマクロ _RWSTD_ALLOCATOR を stdcomp.h に用意しています。_RWSTD_ALLOCATOR によって true と評価されれば、コンパイラは標準 C++ ライブラリアロケータを処理することができます。それ以外の場合は、代替アロケータを使用しなければなりません。
_RWSTD_ALLOCATOR を最初に使用するのは、インタフェースを反映させるためにコンテナが使用しなければならない型名を決定するときです。この場合は、以下と同じコードをコンテナクラス定義に挿入します。
#ifdef RWSTD_ALLOCATOR typedef typename Allocator::rebind<T>::other::reference reference; typedef typename Allocator::rebind<T>::other::const_reference const_reference; typedef typename Allocator::rebind<node>::other::pointer link_type;
typedef Allocator::rebind<T>::other value_allocator; typedef Allocator::rebind<node>::other node_allocator; #else typedef typename allocator_interface<Allocator,T>::reference reference; typedef typename allocator_interface<Allocator,T>::const_reference const_reference; typedef typename allocator_interface<Allocator,node>::pointer link_type;
Allocator alloc; typedef allocator_interface<Allocator,T> value_allocator; typedef allocator_interface<Allocator,node> node_allocator; #endif
ここでは、T に関連する型に rebind を使用していることに注意してください。この場合、アロケータがたとえば vector<int, allocator<void> > など、別の型のアロケータテンプレートパラメータでインスタンス化されても、コンテナが有効であることが保証されるため、これが最も安全な方法です。これにより、Rogue Wave のコンテナは安定したものとなっています。また、value_allocator と node_allocator の 2 つの型のアロケータが提供されることも重要です。実際のアロケータをコンテナにアセンブルすることが必要となるのは、おそらくアロケータが必要になったときでしょう。この例では、どのインタフェースが使用されているかにかかわらず、T の allocator::allocate 呼び出し機構は次のようになります。
value_allocator(alloc)::allocate();
この呼び出しで、テンプレートコピーコンストラクタを使用して適当なアロケータを作成し、次に、その allocator に allocate を呼び出します。このアロケータの使用結果の例として、アロケータに保存されていた状態が参照によってコピーコンストラクタに渡され、その結果、この状態は既存のアロケータオブジェクトに保存され、このオブジェクトがコンテナのコンストラクタに渡されます。