binary file の header 読み書き

http://d.hatena.ne.jp/janus_wel/20100216/1266347871 でいってた header / footer を持った binary file の読み書きの header 部分をこんな方向でやる的な指針作りとしてちょいと書いてみた。まず header を扱うための構造体の定義と流し込むための << 演算子の overload から。 friend 指定は構造体定義の中に operator <<() 関数をまとめたいがために ( data の扱い方はその data 自身が知っているべきというきわめて oo 的な理由とあと単純にその方が定義が散らばらないので読みやすい ) 書いてるけど source の見通しが悪くならないなら global 関数として分離してもいいかも ( header 用構造体に read() / write() という member 関数を定義してそれを呼び出すとかするとキレイかな ) 。

// header.h

#include <istream>
#include <ostream>

// refer: http://www.geocities.jp/KY_webid/cpp/language/024.html#use_template_func
template<typename T> inline T constpointer_cast(const void* const p) {
    return static_cast<T>(p);
}
template<typename T> inline T pointer_cast(void* const p) {
    return static_cast<T>(p);
}

struct Header {
    char type[4];
    __int32 size;

    friend std::istream& operator >> (std::istream& in, Header& header) {
        in.read(header.type, 4);
        in.read(pointer_cast<char*>(&(header.size)), sizeof(header.size));
        return in;
    };

    friend std::ostream& operator << (std::ostream& out, const Header& header) {
        out.write(header.type, 4);
        out.write(constpointer_cast<const char*>(&(header.size)), sizeof(header.size));
        return out;
    };
};

で以下のように使う。

#include "header.h"
#include <fstream>
#include <cstring>  // for memcmp
#include <iostream>
#include <iomanip>

using namespace std;

const char* filename = "binaryheader.txt";
int main(void) {
    // output
    Header oheader = {
        {'M', 'I', 'N', 'E'},
        0x12345678
    };

    ofstream fout(filename, ios::binary | ios::trunc);
    fout << oheader;
    fout.close();

    // input
    Header iheader;

    ifstream fin(filename, ios::binary);
    fin >> iheader;
    fin.close();

    // test
    bool mine = std::memcmp(oheader.type, iheader.type, 4) == 0 ? true : false;

    cout << setfill('.') << left
         << setw(20) << "oheader size " << ' ' << oheader.size << endl
         << setw(20) << "iheader size " << ' ' << iheader.size << endl
         << setw(20) << "is mine? " << ' ' << (mine ? "yes" : "no") << endl;

    return 0;
};

これ以上抽象化、というか systematic にやろうとすると例えば以下のように抽象 class から派生させる形になると思うんだけど

// binaryheader.h
struct IBinaryHeader {
    virtual void read(std::istream& in) = 0;
    virtual void write(std::ostream& out) const = 0;
};

std::istream& operator >> (std::istream& in, IBinaryHeader& bh) {
    bh.read(in);
    return in;
}

std::ostream& operator << (std::ostream& out, const IBinaryHeader& bh) {
    bh.write(out);
    return out;
}
// header.h

#include "binaryheader.h"

struct Header : public IBinaryHeader {
    // data
    char type[4];
    __int32 size;

    // implementations for interface
    void read(std::istream& in) {
        in.read(header.type, 4);
        in.read(pointer_cast<char*>(&size), sizeof(size));
    };
    void write(std::ostream& out) const {
        out.write(header.type, 4);
        out.write(constpointer_cast<const char*>(&size), sizeof(size));
    };
};

こうしてしまうと Header 構造体に vtbl への pointer が追加されてしまって初期化に list を使うことができなくなるので残念な雰囲気が漂う。速度的にもちょい落ちると思うけどここは別に速度が求められる処理ではないので ( ここより data の読み書きを tune すべき ) いいとしても構造体の member へつらつらと代入する code が並ぶのはちょっとご免被りたい。というわけで最初に示したものがおれの今の best 。