PPPUC++#12.5

10.6落穂ひろい

途中でめんどくさくなって飛ばしてしまったけど、一つだけ覚えといたほうがよさそうなのまで飛ばしてしまってたので追記

ios::exceptions()

なにかというと、これに設定されているerror(bad, fail, eof, ... goodはってgoodbitはbitといいつつ0なので拾えませんw)がおきると例外が飛んでくるというもの。結構便利。
#そもそも普段こんな使い方しねーとかいうのは置いといて・・・

10.7 Reading a single value

  • 読める限り読む、というのはできた 。ので次の例。
  • 特定の条件に合致するものが入力されるまで繰り返す、というもの。例によって徐々にカイゼンしていく方針で。
cout << "ENTER: ";
int n = 0;
while (cin >> n) {
   if (1 <=n && n <= 10) break; // check range
   cout << "Sorry "
          << n << " is not in the [1:10] range; please try again\n";
}
  • とかいうヤツ。このままだと「とりあえず動きます」版
  • breakがださいって? いやそんなのは表面的な問題ですね
    • あーinteger以外をいれた場合に死ぬってヤツですね
  • アルファベットがきたら、cin がfailしてnはそのままで抜けてしまう(普遍条件を満たせない)
  • EOFがきても同様
  • 困った入力パターンは3種類
  1. 範囲外の数値
  2. 入力なし > EOF
  3. 数値以外のアルファベットとか
  • とれる選択肢はだいたいこんな感じ
  1. 読み込んだコード自身が問題をなんとかする
  2. 例外を投げて誰かに解決してもらう(大抵は強制終了)
  3. 問題を無視する(イヨっ!)
  • 自分が使うだけなら3でいいんだけどね・・・
    • 他人も使うんなら3は論外
  • 1,2 どちらにするか、というのは結構本質的な問題
    • プログラムによっては、明らかな場合もある
  • 入力なし(EOF):ほとんどのプログラムは「なんともしようが無い」
    • cinを再オープン?
    • ユーザが「もう入力しない!」って言ってんのに、「数字を入力してもらう」ルーチンがどうせいと・・・
      • というわけで、誰か他の人が拾ってくることを願いつつ throw !
  • 結局「例外を投げるか、ローカルに解決するか」の選択ではなくて、「私はどれを解決すべきか」というのの選択
10.7.1 Breaking the problem into manageable parts
  • というわけで、failのときと badのときに対応するわけですが、それをモノリシックに書くと、大変長いしダサい。
 while (true){
   cin >> n;
   if (cin) {
     /*   :  */
     /* range check & sorry messages (SAME AS ABOBE) */
     /*   :  */
   } else if (cin.fail()) {
     cin.clear();
     cout << "Soorry, that was not a number; please try again .." << endl;
     char ch;
     while (cin >> ch && !isdigit(ch)) ; 
     if (!cin) error("no input"); // can be integrated to catch block...
     cin.unget();
   } else /* it's bad or bad eof */
     error("no input");
   }
 }
    • 毎回数字を読もうとするたびにこれ書くのか!??!?!? という奴ですね
  • というわけでちゃんぽんを分解して、一つ一つのアクションにまとめて部品化しましょう
    1. リード
    2. プロンプト
    3. エラーメッセージ
    4. 不正文字の読み飛ばし
    5. 入力値の範囲チェック
  • と分解。
    • まずは4から順に分割していくのが続く。結構退屈。
    • (略)
      • 個人的にはエラー入力が複数行続く場合は複数エラーが出てほしい気が。本書のバージョンは skip_to_int()が
  while(cin>>ch && !isdigit(ch));
      • と数値以外をことごとく殲滅するので、一度「数字じゃない」といったらあとは「わしは同じことは二度も言わん」になってしまうので(それはそれで正しい気もするが返事はやっぱりしてほしい)
      • あと、chを外に出すの嫌い、というわけで自分バージョンはこうした
void skip_to_int() throw(ios::failure)
{
    cin.clear();
    for (char ch; (ch = cin.get()) && (!isdigit(ch) && ch != '\n');) { }
    cin.unget();
}
      • あとせっかく cin::exceptionsやったので、これ使って欲しいような・・・
    cin.exceptions(ios::badbit|ios::eofbit);
    try {
         :
         :
    } catch (const ios::failure& e) {
        cout << "no input" << endl;
    }
10.7.2 Separating dialog from function
  • なんぞ汎用性を高める話
    • メッセージを分離ですね。
    • というわけで自分でも書いてみましたバージョンはまとめてこんな感じ。
#include <iostream>
#include <string>
#include <stdexcept>
#include <limits>
#include <sstream>

using namespace std;

void skip_to_num() throw(ios::failure)
{
    cin.clear();
    for (char ch; (ch = cin.get()) && (!isdigit(ch) && ch != '\n');) ;
    cin.unget();
}

//
// Retrieve a value in specified range
//
template <class T>
T get_num(const T min = numeric_limits<T>::min(), 
          const T max = numeric_limits<T>::max(), 
          const string& prompt = "is not in the range, please try again .. " )
{
    while (true) {
        T n;
        cin >> n;
        if (cin) {
            if (min <= n && n <= max) return n;
            cout << prompt << endl;
        } else {
            cout << "Sorry, that was not a number; please try again" << endl;
            skip_to_num();
        }
    }
}

main()
{
    cin.exceptions(ios::badbit|ios::eofbit);
    try {
        int n = get_num(1, 10);
        cout << "your input is " << n << endl;
    } catch (const ios::failure& e) {
        cout << "no input" << endl;
    }
}
      • 関数テンプレートのデフォルト引数は残念ながら ++0x仕様なのだよね。。。もしも範囲指定しない場合 get_num()みたいにする必要あり
      • 更にpedanticにやるならここまでw
diff --git a/validate.cpp b/validate.cpp
index cf6d8f4..73303e9 100644
--- a/validate.cpp
+++ b/validate.cpp
@@ -14,19 +14,43 @@ void skip_to_num() throw(ios::failure)
 }
 
 //
-// Retrieve a value in specified range
+// Generate error message from tmpL
+//  if tmpl has "[]" in the pattern, it'll be replaced
+//  with "[min, max]".
 //
 template <class T>
-T get_num(const T min = numeric_limits<T>::min(), 
-          const T max = numeric_limits<T>::max(), 
-          const string& prompt = "is not in the range, please try again .. " )
+const string& range_err_msg(const string& tmpl, T min, T max)
+{
+    static string msg;
+    static string prev;
+    static T prevmax, prevmin;
+
+    if (tmpl != prev || prevmax != max || prevmin != min) {
+        prev = tmpl;  prevmax = max;  prevmin = min;
+        msg = "";
+    }
+
+    if (msg.empty()) {
+        msg = tmpl;
+        stringstream s;  s << "[" << min << "," << max << "]";
+        msg.replace(tmpl.find("[]"), 2, s.str());
+    }
+    return msg;
+}
+
+//
+// Retrieve a value 
+template <class T>
+T get_num(const T min = numeric_limits<T>::min(),
+          const T max = numeric_limits<T>::max(),
+          const string& prompt = "is not in the [] range, please try again .. " )
 {
     while (true) {
         T n;
         cin >> n;
         if (cin) {
             if (min <= n && n <= max) return n;
-            cout << prompt << endl;
+            cout << range_err_msg(prompt, min, max) << endl;
         } else {
             cout << "Sorry, that was not a number; please try again" << endl;
             skip_to_num();
      • 意味の無い事で遊んでしまった(病院待ち合いで暇だったのですw)