思うだけで学ばない日記 2.0

思うだけで学ばない日記から移転しました☆!よろしくお願いします。

std::istreamで行末まで読み飛ばすスマートな書き方

これはいくつかの書き方があるようで、istをstd::istreamのインスタンスとして、

  1. ist.unsetf(ios::skipws);とした上で、ist >> c(ただしcはistの文字型(通常char))で'\n'が現れるまで読みまくる
  2. ist.get()で'\n'が現れるまで読みまくる
  3. ist.getline(buf, n)で固定長バッファbuf[]に読む
  4. std::getline(ist, s)で行全体を丸ごとstring sに読み込む

とかの方法が思いつくし実際ネットでも見かけたわけだが、
しかし、3.が可能ということはstd::istreamの中に改行文字を探すコードが存在することは明らかだる
よって、1.の方法は、わざわざそれを殺しておいて自前のコードで'\n'を探すという点がいまいちスマートでわない
2.も同様で、行末まで読み込むコードがstd::istreamの中にあるはずなのに、それを利用せずに自前のコードでループを回すというのはいまいちダサい希ガス
じゃあ3.はどうよ?これは固定長バッファはともかく、行の最大長を与えねばならない点がやはりイマイチ。つまり、

  • ist.getline(buf, n)

としたとき(ここでbuf[]は文字の配列、nは文字数)、今居る読み出し地点からn文字以内に'\n'が現れてくれれば良いが、現れなくとも1行読み終えたかのごとくリターンしてくれてしまう。呼び出し元で次の文字が常に次行の先頭だと思い込むとアテがはずれる。

じゃあ4.しかないんジャネ?しかしこれは、入力を与える外部環境にsのサイズを自由にさせてしまうからセキュリティー上あまりよろしくない*1。時間にせよ空間にせよ、当方プログラムの計算量を外部に好きにさせるのは別な意味でイケてない

というわけで、3.の改良を提案汁、

まず3.の呼び出しの後、真に行末まで読んだのか、n文字より長かったので途中まででやめたのかは

  • ist.unget()

後、

  • (ist.get() == '\n')

が真か否かで判定できるから、次のコードが考えられる。
※[2012-07-22追記]このコードは512文字以上の行を入力されたとき行末まで読まないバグがあります。こっちのエントリ参照。

inline bool skip_line(std::istream& ist, int lenMax = 32768)
{
    const char bufsz = 512;
    char buf[bufsz];
    int len = 0;
    do {
      if (!ist.getline(buf, bufsz)) return false;
      len += (bufsz - 1);   // strlen()呼んで1文字精度で求めるまでもないと思うので適当に計算
      ist.unget();
    } while (ist.get() != '\n' && len <= lenMax);
    return (len <= lenMax);
}

istream::unget()の実装はstdioのungetc()と同様と思われるので実行時コストは全く無視し得る*2

さらに、読み込んだ文字を使いもしないのに固定長バッファbuf[]を確保するのがダサいと思われる向きは、次のようにすれば解決できる

inline bool skip_line(std::istream& ist, char findCh = '\n', int lenMax = 32768)
{
    const int blksz = 512;
    int len = 0;
    do {
      if (!ist.ignore(blksz, findCh)) return false;
      len += blksz;   // strlen()呼んで1文字精度で求めるまでもないと思うので適当に計算
      ist.unget();
    } while (ist.get() != findCh && len <= lenMax);
    return (len <= lenMax);
}

この方法の良いところは、もはや'\n'以外にも、findChに指定した文字までを読み飛ばす」がカソタソに実現できてしまうところだる

...という夢を今さっきみたのでこれから試す(マテ;

【補足】

2つ目のstd::ostream::ignore()を使った例は次のようにも書けばもっとスキーリ、

inline bool skip_line(std::istream& ist, char findCh = '\n', int lenMax = 32768)
{
    if (!ist.ignore(lenMax, findCh)) return false;
    ist.unget();
    return (ist.get() == findCh);
}

ここに一つの完成型を見たいや知らんけど、

*1:少なくとも、改行文字を全く含まない数十ギガバイトの行を送りつけられたら当方プログラムをダウンさせられる。

*2:ungetc()の実装は確かK&Rに書いてあった希ガス