file_source, sinkとfile_descriptor_source, sink

使用する上で目立った違いは、改行コードの扱いでした。

file_source, sinkは、ファイル読み込みの際、改行コード"\r\n"を"\n"に変換します。
そして書き込む際、"\n"を"\r\n"に変換して出力します。
これはstd::ifstream, ofstreamと同じ動作です。

file_descriptor_source, sinkは、ファイル読み込み、そして書き込みの際、改行コードを変換することはありません。
"\n"は"\n"、"\r\n"は"\r\n"として入出力します。


"\r\n"も"\n"も"\n"として扱えるので、file_source, sinkのほうが使いやすそうですね。

ただ、ドキュメントには、file_descriptor_source, sinkはナロー文字とワイド文字間の変換を行わないので、file_source, sinkよりもこちらを使ったほうがよい、というようなことが書かれています。

STLコンテナをSink/Sourceとして使う

Iostreamsでは、コンテナを入力元(Source)、出力先(Sink)として使うことができます。
Sourceとして使うには、boost::make_iterator_range関数を、
また、Sinkとして使うには、boost::iostreams::back_inserter関数を使います。
ファイルに書き出したりせず、アプリケーション内でFilter処理するだけ、ということは多いと思うので、使う機会は多いでしょう。


まずはSink

#include <iostream>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/device/back_inserter.hpp>

int main(){
    namespace io = boost::iostreams;

    std::string result;
    io::filtering_ostream out(io::back_inserter(result));
    out << "Output to container.";
    out.flush();

    std::cout << result << std::endl;

    return 0;
}

Output:

Output to container.

ストリームを使って変数resultに文字列を出力することができました。
boost::iostreams::back_inserterは、Sinkモデル*1のクラスであるboost::iostreams::back_inserter_deviceのインスタンスを返します。


そしてSource

#include <iostream>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/copy.hpp>

int main(){
    namespace io = boost::iostreams;

    std::string source("Input from container.");
    io::filtering_istream in(boost::make_iterator_range(source));
    std::string dest;
    io::copy(in, io::back_inserter(dest));

    std::cout << dest << std::endl;

    return 0;    
}

Output:

Input from container.

変数sourceから文字列を入力することができました。
boost::make_iterator_rangeは、boost::iterator_rangeクラスのインスタンスを返します。Sourceモデル*2ではないです。謎です。


バイナリデータを扱うならstd::stringではなく、std::vectorがよさそうです。

*1:メンバ関数writeもしくはoutput_sequenceを持つ

*2:メンバ関数readもしくはinput_sequenceを持つ

stream, filtering_streamのコンストラクタとパイプライン

これらのコンストラクタはいくつかオーバーロードされてるのですが、
なるべく短いコードで書きたい(I/Oが行える状態にしたい)です。書きましょう。

streamのコンストラクタ引数は、Deviceのコンストラクタ引数と同じものを取ります。

namespace io = boost::iostreams;

io::stream<io::file_sink> file("baby.hs");


filtering_stream(istream/ostream)のコンストラクタは、0以上のFilterのインスタンスと1つのDeviceのインスタンスを引数に取ります。

Filterを使わない場合。

io::filtering_ostream out(io::file_sink("index.html"));

Filterを使う場合。

io::filtering_istream in(
        io::gzip_decompressor() | io::file_source("gzip_file.gz"));

なんかでてきました。パイプラインです。
1つ目のFilterがPipable*1なら|演算子を使ってFilterやDeviceを連結することができます。

*1:そのFilterに|演算子が使えるよう、BOOST_IOSTREAMS_PIPELINEマクロが書かれている

すぐにフィルタ処理を終えたいときはboost::iostreams::closeを使う

filtering_ostream::flushを呼び出すことで処理が完了すると思っていたのですが、そうでない場合がありました。

#include <iostream>
#include <vector>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/copy.hpp>

int main(){
    namespace io = boost::iostreams;
    typedef std::vector<char> Bytes;

    Bytes gzip_data;
    io::filtering_ostream out;
    out.push(io::gzip_compressor());
    out.push(io::back_inserter(gzip_data));
    out << "Good morning, world!";
    out.flush();
    
    io::filtering_istream in;
    in.push(io::gzip_decompressor());
    in.push(boost::make_iterator_range(gzip_data));
    std::string deco_str;
    try{
        io::copy(in, io::back_inserter(deco_str));
    }
    catch(const std::exception& e){
        std::cout << "Exception: " << e.what() << std::endl;
    }

    std::cout << deco_str << std::endl;

    return 0;
}

Output:

Exception: gzip error


さて、gzip errorです。展開ができず発生しました。
これは圧縮とコンテナへの書き込みの処理が完了していないうちに、入力処理が行われたためです。

boost::iostreams::closeを使えばOKです。

Bytes gzip_data;
io::filtering_ostream out;
out.push(io::gzip_compressor());
out.push(io::back_inserter(gzip_data));
out << "Good morning, world!";
io::close(out); //boost::iostreams::closeを使う

これでようやくfiltering_ostreamの処理が完了します。


gzip圧縮Filterは、ヘッダをSinkに書き出す、圧縮したデータをSinkに書き出す、と処理を2回に分けて行っています。
flushした後のgzip_data変数には10バイト、ヘッダ部しかありません。
closeは、すべての処理を完了させてストリームを閉じます。
flushは、Sinkまでの処理が1回行われたら終わりなようです。

Boost.IostreamsのFilterを使う!

Boost.IostreamsにはFilterというものがあります。
これを使うことでストリームから入力、ストリームへ出力するデータを加工できます。

#include <iostream>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>

int main(){
    namespace io = boost::iostreams;

    //データをgzip圧縮してファイルに書き出し
    io::filtering_ostream out;
    out.push(io::gzip_compressor());
    out.push(io::file_descriptor_sink("gzip_file.gz"));
    out << "Example characters";
    io::close(out); //データをflush

    //ファイルからデータを読み込んでgzip展開
    io::filtering_istream in;
    in.push(io::gzip_decompressor());
    in.push(io::file_descriptor_source("gzip_file.gz"));

    std::string str;
    in >> str;

    std::cout << str << std::endl;

    return 0;
}

Output:

Example

"Example characters"と出力されそうな気がしますが、これは>>演算子を使ったため、std::cinと同じようにスペースを区切りとして出力されました。
ひとまず、これはおいておきます。


上記のコードでは、まずfiltering_ostreamに、DualUseFilter*1であるgzip_compressorをpushしました。
そしてファイルへ出力するSinkであるfile_descriptor_sinkをpushしました。
これで、データを出力するとき、データはgzip_compressorを通って圧縮され、file_descriptor_sinkに流れてファイルに出力されます。
filtering_ostreamでは、データはFilterを通ってSinkへと、pushした順番に流れていきます。

次に、filtering_istreamに、DualUseFilterのgzip_decompressorを、そしてSourceのfile_descriptor_sourceをpushしました。
file_descriptor_sourceでファイルからデータが読み込まれ、gzip_decompressorで展開されて、変数に入力されます。
filtering_istreamでは、pushしたFilterからSourceとは逆の順番で、データが流れていきます。


SinkもSourceも、必ず一番最後にpushします。これがデータの流れ着く先、湧き出る元となるので。

ここではFilterはひとつだけ使いました。Filterは0個以上使える、ということになっています。
つまりFilterを何も使わず、Deviceのみをpushし、io::filtering_streamをio::streamと同じく扱うこともできます。
(ただ、Deviceしか使わないなら、io::streamを使ったほうが余計な機能がなくていいかと思います)
そして、Filterを複数個使えば、文字列中の単語を置き換えて、データを圧縮して、暗号化して…といくつものデータ加工を行うことができます。

Filterの解説はここまでです。


さて、おいておかれてしまった、文字列"Example characters"の出力ですが、これは単純に、std::cinと同じようにstd::getlineで取得すればOKです。

int main(){
    namespace io = boost::iostreams;

    io::filtering_istream in;
    in.push(io::gzip_decompressor());
    in.push(io::file_descriptor_source("gzip_file.gz"));

    std::string str;
    std::getline(in, str);

    std::cout << str << std::endl;

    return 0;
}


せっかくなのでIostreamsにある関数、copyを使ってみましょう。

#include <boost/iostreams/copy.hpp>

int main(){
    namespace io = boost::iostreams;

    io::filtering_istream in;
    in.push(io::gzip_decompressor());
    in.push(io::file_descriptor_source("gzip_file.gz"));

    std::string str;
    io::copy(in, io::back_inserter(str));

    std::cout << str << std::endl;

    return 0;
}

copyは1行といわず全データをコピーします。


ところでこのFilterは定期的に掃除をしなくても大丈夫なのかしら。*2

*1:InputFilterとしてもOutputFilterとしても使えるFilter

*2:大丈夫です!!

Boost.Iostreamsでストリーム作成!

Boost.Iostreamsでできることのひとつとして、ストリームの作成が簡単にできる、というのがあります。

それには、まず入出力するデータをやりとりするクラスを作ります。
(このクラスをDeviceといいます。また、出力DeviceはSink、入力DeviceはSourceと呼ばれています)
そして、それをストリーム型に渡せばOKです。

ここでは文字列に☆をつけて女子力を上げるストリームを作成します。

#include <iostream>
#include <boost/iostreams/stream.hpp>
#include <boost/algorithm/string/replace.hpp>

//Sinkクラスを作成するためには、
//boost::iostreams::sinkを継承し、メンバ関数writeを定義します
class GirlSink : public boost::iostreams::sink{
private:
    std::ostream* out_;

public:
    GirlSink(std::ostream* out) : out_(out){}
    
    std::streamsize write(const char* s, std::streamsize n){
        //行の末尾に'☆'をつける
        std::string str(s, n);
        boost::algorithm::replace_all(str, "\n", "☆\n");
        if(*(std::end(str) - 1) != '\n'){ str.append("☆"); }

        *out_ << str;

        return n;
    }
};

int main(){
    namespace io = boost::iostreams;

    GirlSink girl(&std::cout);
    io::stream<GirlSink> out(girl);
    out << "こんにちはみなもです" << std::endl;
    out << "よろしくね" << std::endl;

	return 0;
}

Output:

こんにちはみなもです☆
よろしくね☆


…あがりましたか?

2012/06/23追記
IostreamsにはDeviceのほかFilterというものがあります。
ここではDeviceでデータの加工と出力を行いましたが、役割を考えると、
DeviceはI/Oする対象(たとえばファイル)とデータを入出力する、Filterでデータの処理をする、と分けたほうがいい気がします。

Boostで利用するzlibライブラリのビルド(Windows)

Boost.Iostreamsを利用してgzip圧縮・展開をすることができます。
そのためにはzlibというライブラリをビルドする必要があります。
UNIX系のOSではbjamを実行した際、デフォルトでビルドされるよう設定されているのですが、Windowsでは無効になっています。
UNIX系ではzlibのインストールされているディレクトリを自動で見つけられますが、Windowsではそうはいかないため、ですね。


ビルドするまでの手順を示します。

1.zlibのサイトからzlibを落としてきます。
http://zlib.net/
お好きなところに展開しておきましょう。
ここでは"C:\zlib"に展開したことにします。
2.bjam.exeを利用してビルドします。コマンドプロンプト使いましょう。
bjam -sNO_ZLIB=0 -sZLIB_LIBPATH="C:\zlib" -sZLIB_SOURCE="C:\zlib"
("-s"は続けて指定した変数名の値を設定するオプションです。)


上手にできました。
libbzip2ライブラリには触れませんでしたが、同じような形でインストールできます。*1
他、インストールの詳しい説明はBoost.Iostreamsドキュメント内のInstallationのページを参照。
http://www.boost.org/doc/libs/1_49_0/libs/iostreams/doc/index.html

*1:libbzip2を落としてきて、変数NO_BZIP2とBZIP2_LIBPATHとBZIP2_SOURCEをいじりましょう!