既存の標準 C++ ライブラリコンテナを使用して、ユーザー独自のコンテナを作成する方法を説明します。たとえば、本質的にソートされない一意の値を要求する set コンテナを実装するとします。同時に、アルゴリズムグループをその set に適用するとします。コンテナは、確かにシーケンスですが、連想コンテナは定義によりソートされるため、連想コンテナではありません。アルゴリズムセットが要求する反復子によって、これらのアルゴリズムを適用できるコンテナの範囲が決定されるため、反復子の型が適当な他のシーケンスがあるとすれば、アルゴリズムはこれらのシーケンスには有効な場合があります。アルゴリズムが順方向反復子のみを要求する場合、このアルゴリズムは一般的に使用することができます。これに対して、ランダムアクセス反復子を要求する場合は、アルゴリズムの使用は非常に制限されます。
この set コンテナの単純な実装では、その多くの機構に既存の標準 C++ ライブラリコンテナを利用することができます。このコードの再使用は、以下の 3 とおりの方法で可能です。
継承
汎用継承
汎用合成
以降の節で、各方法を検討します。
継承は強力なオブジェクト指向プログラミングの機能であり、これによってオブジェクトは、他のオブジェクトから属性と動作を派生させることができます。ユーザー独自のコンテナを作成するには、既存の標準 C++ コンテナからコンテナを派生させ、特定の関数を置き換えて希望の動作を実現させます。以下に示すのは、vector コンテナから派生させるアプローチです。
#include <vector> // 標準名または大域名との衝突を避けるために // 名前空間を使用していることに注意 namespace my_namespace { template <class T, class Allocator = std::allocator> class set : public std::vector<T,Allocator> { public: // insert などの関数を置き換える iterator insert (iterator position, const T& x) { if (find(begin(),end(),x) == end()) return vector<T,Allocator>::insert(position,x); else return end(); // この値はすでに存在している } }; } // my_namespace の終わり
ユーザー独自のコンテナを作成する第 2 の方法では、vector を指定する代わりに汎用アダプタを作成します。この場合は、テンプレートパラメータを使用して基本コンテナを作成します。
namespace my_namespace { template <class T, class Container = std::vector<T> > class set : public Container { public: // 型定義を指定する (反復子は説明のためにのみ指定) typedef typename Container::iterator iterator; // insert などの関数を置き換える iterator insert (iterator position, const T& x) { if (find(begin(),end(),x) == end()) return Container::insert(position,x); else return end(); // この値はすでに存在している } }; } // my_namespace の終わり
アダプタによる汎用継承を使用する場合、アダプタのインスタンス化に使用されるコンテナからデフォルトの機能と動作以上のものを期待することはできません。アダプタまたはそのユーザーが基本コンテナの提供する機能以上のものを期待している場合は、何が期待できるかをマニュアルに正確に明記しておく必要があります。
ユーザー独自のコンテナを作成する第 3 の方法は、継承の代わりに合成を使用します。この方法の意図は、標準 C++ ライブラリのアダプタである queue、priority queue、stack に見ることができます。汎用合成を使用する場合、希望のインタフェースをすべて実装する必要があります。コンテナの提供するインタフェースのサブセットだけを提供することによってアダプタの動作を制限する場合、これが最も便利なオプションです。
namespace my_namespace { template <class T, class Container = std::vector<T> > class set { protected: Container c; public: // 必要な型定義を指定 typedef typename Container::iterator iterator; // insert などすべての必須関数を指定 iterator insert (iterator position, const T& x) { if (find(c.begin(),c.end(),x) == c.end()) return c.insert(position,x); else return c.end(); // この値はすでに存在している } }; } // my_namespace の終わり
コンテナを既存のコンテナに適合させる利点はたくさんあります。たとえば、適合させるコンテナの実装や仕様を再使用できることもその 1 つです。