More C++ Idioms/反復子対(Iterator Pair)
反復子対(Iterator Pair)
[編集]意図
[編集]ある範囲のデータ値を、そのデータ構造を意識することなく指定する。
別名
[編集]動機
[編集]コピーコンストラクタを使って、別の vector<int> から vector<int> を作成することが便利であることはよく知られている。 同様に、メンバテンプレートによる型変換(Coercion by Member Template)イディオムを使ってメンバテンプレートコンストラクタにより vector<int> から vector<double> を作成することも便利である。 コード例は以下のようになる。
template <class T>
class vector
{
public:
vector (const vector<T> &); // コピーコンストラクタ
template <class U>
vector (const vector<U> &); // メンバテンプレートによる型変換を使ったコンストラクタ
};
vector のインタフェースは、それでもまだある種の要求には十分に柔軟とは言えない。 例えば、list や set や POD の配列そのものから作成することができない。
template <class T>
class vector
{
public:
vector (const list<T> &);
// コンストラクタは list がどのように実装されているかを知っていなければならない。非常に良くない!
vector (const set<T> &);
// コンストラクタは set がどのように実装されているかを知っていなければならない。非常に良くない!
vector (const T * pod_array);
// コンストラクタは pod_array がどこで終端されているかを知らない。非常に柔軟ではない!
};
反復子対(Iterator Pair)イディオムはこの問題を解決する。 このイディオムは、(明白ながら)反復子(Iterator)デザインパターンを基本としている。 反復子(Iterator)パターンの意図は、ある集約的構造を辿るためのオブジェクトを、その構造の実装に対する仮定なしに提供することである。
解法とサンプルコード
[編集]反復子(Iterator)の対は、値の範囲の先頭と終端を指定するために使われる。 反復子(Iterator)デザインパターンの効果により、(例の vector 中で)反復子対イディオムを使うものはどれでも、データ構造を意識することなく範囲をアクセスすることができる。 唯一の要求は、反復子がある決まった最小のインタフェースを公開するべきだという点である。
template <class T>
class vector
{
public:
template <class InputIterator>
vector (InputIterator begin, InputIterator end) // 反復子対(Iterator-pair)コンストラクタ
{
// 必要なメモリを割り当て、mem に格納する。
for (int i = 0; begin != end; ++i)
{
mem[i] = *begin;
++begin;
}
}
};
int main (void)
{
std::list<int> l(4);
std::fill(l.begin(),l.end(), 10); // 反復子対を使って list を埋める
std::set<int> s(4);
std::fill(s.begin(),s.end(), 20); // 反復子対を使って set を埋める。
std::vector<int> v1(l.begin(), l.end()); // 反復子対を使って vector を作成する。
std::vector<int> v2(s.begin(), s.end()); // 別の vector を作成する。
}
反復子対(Iterator Pair)イディオムは、しばしばメンバテンプレートと組み合わせて使われる。これは、反復子の正確な型があらかじめ分からないからである。 set<T>::iteartor や list<T>::iterator、あるいは POD 配列にもなりうる。 型に関わらず、汎用(generic)アルゴリズムは反復子対によって動作するように記述される。 反復子の型がモデル化するべきコンセプトを示しておくと便利なことが多い。 前記の例では、反復子が、少なくとも入力反復子(InputIterator)コンセプトをモデル化していなければならない。 反復子のカテゴリ(タグ)とその使用法についてのより詳細な情報は、タグによる分配(Tag Dispatching)イディオムで記述している。
反復子対(Iterator Pair)イディオムが避けられない場合がある。 例えば、std::string を、内部に 0 を持つキャラクタ列のバッファから作成しようとする場合には、反復子対(Iterator Pair)イディオムを使わざるを得ない。
char buf[] = { 'A', 'B', 0, 'C', 0, 'D'};
std::string str1 (buf); // "AB" だけが作成される。
std::string str2 (buf, buf + sizeof (buf)); // 反復子対を利用する。"AB_C_D" が作成される。
// buf は範囲の先頭で、buf + sizeof(buf) が範囲の終端
std::cout << str1 << " length = " << str1.length() << std::endl; // AB length = 2
std::cout << str2 << " length = " << str2.length() << std::endl; // AB_C_D length = 6
既知の利用
[編集]全ての標準コンテナ