C++編【標準ライブラリ】 第31章 ストリームイテレータ

先頭へ戻る

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

ストリームイテレータ

第26章で、逆イテレータと挿入イテレータという2つのイテレータアダプタを紹介しました。 本章では、3つ目のイテレータアダプタとして、ストリームイテレータ(ストリーム反復子)を取り上げます。

ストリームイテレータは、これを介した読み書きを、ストリームに対する入出力に変換するイテレータアダプタです。 出力用の ostream_iterator と、入力用の istream_iterator の2種類が存在します。 いずれも、使用するためには、iterator という標準ヘッダをインクルードします。

ostream_iterator

ostream_iterator は、以下のように定義されたクラステンプレートです。

namespace std {
	template <typename T, typename CharT = char, typename Traits = char_traits<CharT> >
	class ostream_iterator : public iterator<output_iterator_tag, void, void, void, void> {
	};
}

3つあるテンプレートパラメータのうち、2つ目と3つ目は、出力対象になるストリームのクラステンプレートに与えるテンプレート引数と一致させます。
1つ目のテンプレートパラメータは、出力する値の型を指定します。 この指定が必要であることから分かるように、ストリームイテレータを介した出力は、使える型が固定されます。

基底クラスの iterator は、イテレータが共通して持つべき機能を定義したクラステンプレートですが、 特に意識する必要は無いので、ここでは詳しくは取り上げません。

ostream_iterator の動作を確認してみましょう。

#include <iostream>
#include <iterator>

int main()
{
	std::ostream_iterator<int> osIt(std::cout, "\n");

	osIt = 100;
	osIt = 200;
}

実行結果:

100
200

ostream_iterator のコンストラクタには、出力先となるストリームのオブジェクトを渡します。
また、省略可能な第2引数があり、ここには1回の出力のたびに出力される区切り文字列を指定できます。 省略した場合は、区切り文字列は出力されません。 上のサンプルプログラムでは "\n" を指定しているので、出力のたびに改行が行われています。

出力を行うには、単に =演算子を使って代入するだけです。 ostream_iterator を使った「it = value」という式は、出力先のストリームオブジェクトに対して <<演算子を呼び出します。 また、前述のように、区切り文字列の指定があれば、その出力が追加で行われます。

ostream_iterator は、出力イテレータに分類されるので(第14章)、 *演算子による間接参照や ++演算子が定義されていますが、*演算子も ++演算子も、特に何も行わず、自分自身への参照を返すだけです。 使っても問題は無いので、他のイテレータとコードを統一する意味で、これらの演算子を使っても良いです。
例えば、次のように書いても、動作には影響がありません。

*osIt = 100;
++osIt;
*osIt = 200;
++osIt;


さて、実際には、ostream_iterator は STLアルゴリズムと組み合わせて使うことが一般的です。 例えば、以下のサンプルプログラムでは、copy関数(第20章)を使って、 vector の内容を標準出力ストリームへ出力します。

#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>

int main()
{
	std::vector<int> v;
	v.push_back(0);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, "\n"));
}

実行結果:

0
1
2
3
4

istream_iterator

istream_iterator は、以下のように定義されたクラステンプレートです。

namespace std {
	template<typename T, typename CharT = char, typename Traits = char_traits<CharT>, typename Distance = ptrdiff_t>
	class istream_iterator : public iterator<input_iterator_tag, T, Distance, const T*, const T&> {
	};
}

4つあるテンプレートパラメータのうち、2つ目と3つ目は、入力を行うストリームのクラステンプレートに与えるテンプレート引数と一致させます。
1つ目のテンプレートパラメータは、入力する値の型を指定します。 この指定が必要であることから分かるように、ストリームイテレータを介した出力は、使える型が固定されます。
4つ目のテンプレートパラメータは、2つのイテレータの差(距離)を表すために使う型を指定します。

基底クラスの iterator は、イテレータが共通して持つべき機能を定義したクラステンプレートですが、 特に意識する必要は無いので、ここでは詳しくは取り上げません。

istream_iterator の動作を確認してみましょう。

#include <iostream>
#include <iterator>

int main()
{
	std::istream_iterator<int> isIt(std::cin);
	std::istream_iterator<int> isItEnd;

	while (isIt != isItEnd) {
		std::cout << *isIt << std::endl;
		++isIt;
	}
}

実行結果:

1       <-- 標準入力
1
1 3 5 a   <-- 標準入力
1
3
5

istream_iterator のコンストラクタには、入力元となるストリームのオブジェクトを渡します。 あるいは、引数を指定しなければ、ストリームの末尾を表すオブジェクト(end-of-streamイテレータ)を得られます。

istream_iterator は、入力イテレータに分類されるので(第14章)、 *、->、++、==、!= の各演算子が定義されています。

実際の入力は、基本的には ++演算子を使用したときに行われます。 あるいは、引数付きのコンストラクタを呼び出したときに、最初の入力を行うこともあります。 また、*演算子を使用したときに、入力済みの値が無ければ、返せる値が無いので、このタイミングで入力が行われます。

int型を扱う istream_iterator なので、数値以外の入力を受け取るとエラーになります。 エラーになると、istream_iterator は end-of-streamイテレータになるため、whileループの条件式が偽になって抜け出し、 プログラムは終了します。


ostream_iterator と同様、istream_iterator も STLアルゴリズムと組み合わせることができます。 以下のサンプルプログラムは、copy関数(第20章)を使って、文字列ストリームから読み取った値を、list に格納しています。 なお、list へ要素を追加するときには、末尾挿入イテレータ(第26章)を利用しています。

#include <iostream>
#include <sstream>
#include <iterator>
#include <algorithm>
#include <list>
#include <string>

namespace {
	void Println(int elem)
	{
		std::cout << elem << std::endl;
	}
}

int main()
{
	const std::string s("123 456 789");
	std::list<int> lst;

	std::istringstream iss(s);
	std::istream_iterator<int> isIt(iss);
	std::istream_iterator<int> isItEnd;
	std::copy(isIt, isItEnd, std::back_inserter(lst));

	std::for_each(lst.begin(), lst.end(), Println);
}

実行結果:

123
456
789

ストリームバッファイテレータ

ストリームイテレータの特殊な形として、ストリームバッファイテレータがあります。 これは、ストリームと結びついているストリームバッファに対して、1文字単位での読み書きを行えます。

ストリームバッファを直接アクセスするので、扱う型は常に文字の型になります。 また、同じ理由から、大抵の場合は、ストリームイテレータよりも高速に動作します。

ストリームバッファイテレータには、出力用の ostreambuf_iterator と、入力用の istreambuf_iterator の2種類が存在します。 いずれも、使用するためには、iterator という標準ヘッダをインクルードします。

ostreambuf_iterator

ostreambuf_iterator は、以下のように定義されたクラステンプレートです。

namespace std {
	template <typename CharT, typename Traits = char_traits<CharT> >
	class ostreambuf_iterator : public iterator<output_iterator_tag, void, void, void, void> {
	};
}

テンプレートパラメータは、出力対象に指定するストリームのクラステンプレートに与えるテンプレート引数と一致させます。

基本的な使い方は ostream_iterator と同じです。 ここでは、STLアルゴリズムを使った例を挙げておきます。

#include <iostream>
#include <iterator>
#include <string>
#include <algorithm>

int main()
{
	const std::string s("abcde ... xyz\n");

	std::ostreambuf_iterator<char> osbufIt(std::cout);
	std::copy(s.begin(), s.end(), osbufIt);
}

実行結果:

abcde ... xyz

ostreambuf_iterator を使った出力は、1文字単位で行われ、行などの概念がありません。 行単位という訳ではないため、このサンプルプログラムのように、出力する文字列自体に改行文字が無いと、最後の改行は行われません。

istreambuf_iterator

istreambuf_iterator は、以下のように定義されたクラステンプレートです。

namespace std {
	template<typename CharT, typename Traits = char_traits<CharT> >
	class istreambuf_iterator : public iterator<input_iterator_tag, CharT, typename Traits::off_type, CharT> {
	};
}

テンプレートパラメータは、入力元になるストリームのクラステンプレートに与えるテンプレート引数と一致させます。

基本的な使い方は istream_iterator と同じです。 ここでは、あるファイルの内容を、std::string に格納するプログラムを示します。

#include <iostream>
#include <fstream>
#include <iterator>
#include <string>

int main()
{
	std::ifstream ifs("test.txt");
	std::istreambuf_iterator<char> isbufIt(ifs);
	std::istreambuf_iterator<char> isbufItEnd;

	std::string s(isbufIt, isbufItEnd);

	std::cout << s << std::endl;
}

test.txt

test
test

test

実行結果:

test
test

test

このプログラムで、変数 isbufIt、isbufItEnd を定義せず、直接、std::string の実引数に指定することができますが、 「std::string s(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());」のように書くと、コンパイルエラーになります。 これは、C++ の構文解析の都合で、第1引数の部分が関数宣言であるとみなされてしまうためです。
次のように、括弧を補えば、正しくコンパイルできます。
「std::string s((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());」
ただ、元のプログラムのように、一旦変数を経由させた方が分かりやすいと思われます。


練習問題

問題@ 標準入力から、空白で区切られた複数の文字列を受け取り、入力された順番と逆順になるように標準出力へ書き込むプログラムを、 istream_iterator、ostream_iterator を使って作成して下さい。

問題A 問題@のようなプログラムを、ストリームバッファイテレータを使って作成することは適切と言えるでしょうか?


解答ページはこちら

参考リンク

更新履歴

'2017/2/13 新規作成。



前の章へ

次の章へ

C++編のトップページへ

Programming Place Plus のトップページへ