17
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

特殊なイテレータを使おう

Last updated at Posted at 2015-11-20

はじめに

みなさんこんにちは。

通常イテレータといえば、まず思いつくのはコンテナの要素を順々にたぐるアレだと思いますが、STLにはそうでないイテレータもあります。それはイテレータによってアルゴリズムを汎用的に書けるようになったことで可能になった応用的なイテレータですが、非常に有用な反面とっつきにくいイメージがあります。そこで、この記事ではSTLに存在する特殊なイテレータをSTLに存在する通常のアルゴリズムを使って紹介していきたいと思います。

reference:

http://en.cppreference.com/w/cpp/iterator
http://en.cppreference.com/w/cpp/algorithm

通常のイテレータ

イテレータがゲシュタルト崩壊したときに見てください。

std::vector< int > vec { 1, 2, 3, 4, 5 };
typename std::vector< int >::iterator itr = begin(vec);

// output
*itr = 5; // assign value

++itr; // points to the next element.

// input
int n = *itr; // 2

ostream_iterator, ostreambuf_iterator

category: outputiterator

ostream_iteratorは出力ストリームの現在位置を指すイテレータです。イテレータに値を設定することはストリームに値を流すことと同義と言えます。また、出力演算子が定義されている型であれば何でも扱うことができます。
streambuf_iteratorはストリームに関連付けられたバッファを指すイテレータです。ストリームイテレータと違い、バッファに直接値を突っ込むため効率はいいですが、当然ながらバッファと同じ型しか与えることができません。

std::ostream_iterator< int > itr(std::cout);
*itr = 123; // std::cout << 123;
++itr; // nop

std::vector< int > vec{4, 5, 6};
std::copy(begin(vec), end(vec), itr); // outputs 456

std::ostreambuf_iterator< char > bufitr(std::cout.rdbuf()); // typeof the cout's buffer is char.
*bufitr = 'h'; // std::cout.rdbuf()->sputc('h');
std::string str = "ello world";
std::copy(begin(str), end(str), bufitr); // outputs ello world

istream_iterator, istreambuf_iterator

category: inputiterator

入力ストリームの現在位置を指すイテレータです。イテレータから値を読み出すことはストリームから値を読むことと同義です。また、イテレータを進めることはストリームの現在位置を進めることと同義です。
コンストラクタに何も与えないと、終端を意味するイテレータを作成できます。

std::istringstream iss("1 2 3 4 5 6");
std::istream_iterator< int > itr(iss);
int n = *itr; // 1
std::cout << n << std::endl; // outputs 1
++itr; // points to the next element

int m = std::accumulate(itr, {}, 0); // 0 + 2 + 3 + 4 + 5+ 6
std::cout << m << std::endl; // outputs 20

std::istream_iterator< int > itr2(iss); // points end
int l = std::accumulate(itr2, {}, 0); // 0
std::cout << l << std::endl; // outputs 0

reverse_iterator

言わずと知れたリバースイテレータなので説明不要かと思います。
C++14からはヘルパ関数を使うことができます。
make_reverse_iterator(C++14)

move_iterator

category: depends on the argument

読み取り専用のイテレータです。このイテレータから読み込まれる値はムーブされて渡されます。
ヘルパ関数のmake_move_iteratorが用意されています。
異なるコンテナ間で内容をムーブするのに便利ですね。

std::vector< std::string > vec {"hello", "beautiful", "world"};

std::list< std::string > list(
    std::make_move_iterator(begin(vec)),
    std::make_move_iterator(end(vec)));
 
// output nothing
std::copy(begin(vec), end(vec),
    std::ostream_iterator< std::string >(std::cout, " "));
 
// outputs hello beautiful world 
std::copy(begin(list), end(list),
    std::ostream_iterator< std::string >(std::cout, " "));

back_insert_iterator

category: outputiterator

通常であればoutputiteratorが必要とされるところでも、出力先のコンテナには予め領域が確保されてなければなりません。容量が足りずに出力先のコンテナのend()の指す場所に突入した時点でバッファオーバーフローが起こってしまうからです。

std::vector< int > vec1 {1, 2, 3};
std::vector< int > vec2;
// vec2.resize(vec1.size());
std::copy(begin(vec1), end(vec1), begin(vec2)); // oops!

このイテレータは割り当てられた値をpush_backで追加してくれるので、そのような心配はありません。
ただし、コンテナがpush_back関数を持っている必要があります。
また、便利なヘルパ関数back_inserterを使うことができます。

std::vector< int > vec1 {1, 2, 3, 4, 5};
std::vector< int > vec2;

std::copy(begin(vec1), end(vec1), std::back_inserter(vec2));
// outputs 12345
std::copy(begin(vec2), end(vec2), std::ostream_iterator< int >(std::cout));

front_insert_iterator

category: outputiterator

back_insert_iteratorと基本的に同じものですが、back_insert_iteratorがpush_backで要素を追加するのに対して、push_frontを使って要素を追加してくれます。よって、コンテナはpush_frontを持っている必要があります。
back_insert_iterator同様にヘルパ関数が用意されています。front_inserter

std::list < int > list1 {1, 2, 3, 4, 5};
std::list < int > list2;

std::copy(begin(list1), end(list1), std::front_inserter(list2));
// outputs 54321
std::copy(begin(list2), end(list2), std::ostream_iterator< int >(std::cout));

insert_iterator

category: outputiterator

お察しの通り、insertを使って要素を追加してくれるイテレータです。やはりお察しの通り、コンテナはinsertを持っている必要があります。
ヘルパ関数はinserterを使うことができます。

std::list < int > list1 {3, 4, 5, 6};
std::list < int > list2 {1, 2, 7, 8};

auto itr = begin(list2);
std::advance(itr, 2);
std::copy(begin(list1), end(list1), std::inserter(list2, itr));
// outputs 12345678
std::copy(begin(list2), end(list2), std::ostream_iterator< int >(std::cout));

おわりに

いかがだったでしょうか。紹介したのは嫌われやすいイテレータたちですが、アルゴリズムヘッダに入っているアルゴリズムをフル活用できるようになるのでぜひ使ってあげてくださいね。
また使用する際は、カテゴリに注意してください。彼らは基本的に(カテゴリとして)低機能なイテレータですから、自分より高機能なイテレータを求めているアルゴリズムに突っ込むことはできません。

間違いなど気付いた点がございましたらコメントの方へよろしくお願いします。

17
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?