バイナリ列をstd::ifstreamで読み込む。

バイナリのみのデータをstd::ifstreamで読み込む時にはまったのでメモ。 # 意外とというか忘れている。。。

バイナリデータの読み込み(失敗)

例えば32bitのデータが連続している下記のようなデータがある。

test.dat

12345678 00112233 44556677 8899aabb ccddeeff
-------- -----------------------------------
00000000:00000000 00000001 00000002 00000003
00000010:00000004 00000005 00000006 00000007
00000020:00000008 00000009 0000000a 0000000b
00000030:0000000c 0000000d 0000000e 0000000f

数値で0-fまで32bitずつ。この書き方だとbig endianになる。

以下のようなコードで読み込めると思っていたんだけど。 思ったように行かない。

std::ifstream ifs("./test.dat", std::ios::binary);
std::vector<std::int32_t> v;
v.assign(std::istream_iterator<std::int32_t>(ifs),
         std::istream_iterator<std::int32_t>());

std::for_each(v.begin(), v.end(), [](std::int_t i) {std::cout << i << " ";});

ちなみに環境はgcc 4.8.2だけど多分どれもダメ。 oprator >>(std::int_t)が32bitずつ読み込むようなことをしてくれない。

回避策

そういう場合は、32bit幅でreadするようにoperator >>をオーバーロードする。 ただ、std::int32_tの挙動は変えられないので、以下のように変換用のクラスを作る。 このクラスはstd::int32_t型と暗黙に変換できるようにしておく。

class int32_reader
{
public:
    int32_reader(std::int32_t i = -1) : i_(i) {}
    operator std::int32_t(void) const {return i_;}

private:
    friend std::istream& operator >>(std::istream& is, int32_reader& w);
    std::int32_t i_;
};

std::istream& operator >>(std::istream& is, int32_reader& w)
{
    is.read(reinterpret_cast<char*>(&w.i_), sizeof(w.i_));
    return is;
}

int main(int argc, char* argv[])
{
    std::ifstream ifs("./test.dat", std::ios::binary);
    std::vector<std::int32_t> v;
    v.assign(std::istream_iterator<int32_reader>(ifs),
             std::istream_iterator<int32_reader>());

    std::for_each(v.begin(), v.end(),
                  [](std::int32_t i) {std::cout << i << " ";});
    return 0;
}

これで32bitずつデータを読み込めるようになる。

なんかもっと良い方法がありそう。。

おまけ

一応、入出力対応でテンプレート化してみた。