バイナリの一部分をコピー

モチベーション

バイナリファイルの一部分を別ファイルにコピーする簡潔な手段が欲しい.

問題設定

// "in.dat"の[first, last)を"out.dat"へコピーしたい.(first, lastは先頭からのoffset)
size_t first, last;
ifstream ifs("in.dat", ios::in | ios::binary);
ofstream ofs("out.dat", ios::out | ios::trunc | ios::binary);
.....
ifs.seekg(first);
size_t sz = last - first;
// ここの部分をどうすれば良いか?

解法

とりあえず思いついた解法3つ.入力ストリームがsz読むよりも前にEOFに達した場合は例外とし,また出力ストリームがEOFに達した場合も例外とする.

解法その1:get & put

getとputを使って単純に実装してみる.

bool is_in_good, is_out_good;
char ch;
for(size_t count = 0;
  count < sz
    && (is_in_good = !!ifs.get(ch))
    && (is_out_good = !!ofs.put(ch));
  ++count);
if(!is_in_good || !is_out_good){
  throw exception("");
}

(エラーフラグを2つにしているのは入力・出力どちらに例外があったのかを見分けるため)

解法その2:ストリームバッファを直接触

basic_ostream::operator<<(basic_streambuf *)の実装(VC++7.1)をまねして,ストリームバッファを直接触るコードを書いてみる.

typedef char_traits<char> traits_t;
traits_t::int_type eof = traits_t::eof();
traits_t::int_type meta;
streambuf *isb = ifs.rdbuf();
streambuf *osb = ofs.rdbuf();
meta = isb->sgetc();
if((meta = isb->sgetc()) == eof){
  throw exception("");
}
if(osb->sputc(traits_t::to_char_type(meta)) == eof){
  throw exception("");
}
for(size_t count = 1; count < sz; ++count){
  if((meta = isb->snextc()) == eof){
    throw exception("");
  }
  if(osb->sputc(traits_t::to_char_type(meta)) == eof){
    throw exception("");
  }
}
解法その3:istreambuf_iterator & ostreambuf_iterator

istreambuf_iteratorとostreambuf_iteratorの組み合わせ.

bool is_in_good, is_out_good;
istreambuf_iterator<char> iit(ifs);
ostreambuf_iterator<char> oit(ofs);
for(size_t count = 0;
  count < sz
    && (is_in_good = (iit != istreambuf_iterator<char>())) 
    && (is_out_good = !oit.failed());
  ++count, ++iit, ++oit){
  *oit = *iit;
}
if(!is_in_good || !is_out_good){
  throw exception("");
}

比較実験

以下のような環境で上3つのコードを比較してみる.

  • 18ファイル(合計約178MB)の各々に埋め込まれたPCMデータを抽出(何のデータかは聞かないで('A`))
  • 実験機はCeleron 1GHz,メモリ256MB(新しいの( ゜д゜)ホスィ…)
  • コンパイラMicrosoft Visual C++ Toolkit 2003, Release Configuration, /Ox付き
  • ストリームクラスはboost::filesystem::ifstream及びboost::filesystem::ofstream
  • boost::filesystemのライブラリをリンク

以上のような条件における各コードの実行時間を計測.結果は以下.

get & putストリームバッファを直接触istreambuf_iterator & ostreambuf_iterator
137s60s62s

結論

istreambuf_iterator & ostreambuf_iteratorでいいや.

余談

get & putが遅い理由が見当たらない・・・.実装読んだ限りでは他2つとそう変わりないはずなのに・・・.
っていうかこんなことしてるから貴重な休日が潰れるんですよ!ヽ(`Д´)ノ ウワァァン!!
後,某所のアドバイスに感謝.