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

トップページC++編

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 と更新されており、今後も 3年ごとに更新されます。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。

この章の概要

この章の概要です。


ストリームイテレータ

第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::strings(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 を使って作成してください。

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


解答ページはこちら

参考リンク


更新履歴

’2017/2/13 新規作成。



前の章へ (第30章 入出力の書式化とマニピュレータ)

次の章へ (第32章 アロケータ)

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

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る