## 11.5 抽出子と挿入子のさらに高度な応用

挿入と抽出は、入出力ストリームの枠組とはまだ完全には適応していません。組み込み型の挿入子と抽出子は、書式設定フラグで制御することができます。 これらは、Rogue Wave の演算子では完全に無視される部分です。Rogue Wave の演算子は、挿入時にはフィールド幅を考慮しません。また抽出時には空白をスキップします。

また、エラー通報も無視します。たとえば、抽出された日付が 2 月 31 日の場合、原因がはっきりしないまま基本バッファ外部デバイスにアクセスできないために挿入時に失敗した場合、ファセットから例外が送出された場合などです。こういったことを防ぐには、ストリームの個々の状態に状態ビットを設定し、例外マスクの指示に従って例外を送出、再送出します。

ここで、もっと基本的な、挿入子と抽出子は何をするのかということを説明します。

書式設定フラグに関して、挿入子と抽出子は次のように機能します。

- 挿入子と抽出子の先頭には監視オブジェクトを作成します。そのコンストラクタとデストラクタでは、監視オブジェクトは、空白のスキップや連結ストリームのフラッシュといった一連の標準タスクを実行します。詳細は、『標準 C++ クラスライブラリ・リファレンス』を参照してください。
- 使用後に幅をリセットします。

状態ビットに関して、挿入子と抽出子は次のように機能します。

- ストリームバッファに問題が発生すると、`badbit` を設定します。
- 構文解析や書式設定そのものが失敗すると、`failbit` を設定します。
- 入力シーケンスの最後に、`eofbit` を設定します。

例外マスクに関して, 挿入子と抽出子は次のように機能します。

- `setstate()` 関数でストリームのエラー状態を設定します。ストリームの例外マスクの例外スイッチに従って、`ios_base::failure` 例外を自動的に送出します。
- 構文解析や書式設定時に送出された例外を捕獲し、`failbit` や `badbit` を設定し、元の例外を再送出します。

ロケールに関して、挿入子と抽出子は次のように機能します。

- ストリームバッファのロケールではなく、ストリームのロケールを使用します。ストリームバッファのロケールは、主にコード変換に使用します。したがって、ストリームと新しいロケールを組み合わせても、その内容はストリームのロケールに反映されるだけで、ストリームバッファのロケールには反映されません。15

ストリームバッファに関して、挿入子と抽出子は次のように機能します。

- 抽出子や挿入子で監視オブジェクトを使用する場合は、書式設定層から関数を呼び出さないでください。監視オブジェクトは、ストリームの mutex (= 相互排他ロック) でストリームをロックするため、マルチスレッドの場合にデッドロックが生じるためです。ストリームのメンバー関数のいずれかに対して入れ子の呼び出しを実行すると、再び監視オブジェクトが作成され、同じ相互排他ロックを待機するので、デッドロックになります。この場合は、ストリームバッファの関数を使用してください。これらの関数は、ストリームの相互排他ロックを使用せず、いずれにしても効率的だからです。

> 注: 監視オブジェクトを挿入子や抽出子に作成した後は、ストリームの入出力関数を呼び出さないでください。代わりにストリームバッファの関数を使用してください。

### 11.5.1 例に対する推奨事項の適用

これまで構築してきた例に戻って、クラス `date` の抽出子と挿入子に手を加えてみます。次に示すのは改善後の抽出子です。

>     template<class charT, class Traits>
>     basic_istream<charT, Traits>& operator >>
>       (basic_istream<charT, Traits >& is, date& dat)
>     {
>      ios_base::iostate err = 0;                                   //1

>      try {                                                        //2

>       typename basic_istream<charT, Traits>::sentry ipfx(is);     //3

>       if(ipfx)                                                    //4
>       {
>        use_facet<time_get<charT,Traits> >(is.getloc())
>             .get_date(is, istreambuf_iterator<charT,Traits>()
>                      ,is, err, &dat.tm_date);                     //5
>        if (!dat) err |= ios_base::failbit;                        //6
>       }
>      } // 実行
>      catch(...)                                                   //7
>      {
>         bool flag = FALSE;
>         try { is.setstate(ios_base::failbit); }                   //8
>         catch( ios_base::failure ) { flag= TRUE; }                //9
>         if ( flag ) throw;                                       //10
>      }

>      if ( err ) is.setstate(err);                                 /11

>      return is;
>     }

//1 | 変数 `err` は、エラーの発生を追跡します。この例では、変数 `err` は `time_get` ファセットに渡り、ここで個々の状態ビットが設定されます。
//2 | 実際の例外の送出前に各エラー状態が正しく設定されるように、抽出子や挿入子内の演算は、すべて try ブロック内に組み込みます。
//3 | 先頭の空白のスキップなど、準備段階のすべての処理を行う監視オブジェクトをここで定義します。
//4 | 準備処理がすべて正しく行われたかどうかを確認します。クラス `sentry` には `bool` への変換機能があり、これで検査を行います。
//5 | これは、ストリームのロケールの構文解析ファセットに対する呼び出しです。抽出子の基本バージョンのものと同じです。
//6 | 日付が意味論的に有効かどうか、たとえば 2 月 30 日といった誤った日付がないかどうかを調べるための機能が、日付クラスにあるものとします。無効な日付が抽出されると、エラーになって、`failbit` が設定されるようにします。
//7 | この場合、ストリームの `setstate()` の関数で `failbit` を設定しないでください。ストリームの例外マスクでオンに設定すると、`setstate()` でも例外が送出されるためです。この時点の例外の送出は必要ないので、 状態変数 `err` に `failbit` を追加します。
//8 | ここで、これまでに送出されたすべての例外を捕獲します。これによって、例外で抽出子が停止する前にストリームのエラー状態を設定し、元の例外を送出します。
//9 | 最後は、ストリームの `setstate()` 関数でストリームのエラー状態を設定します。この呼び出しがあると、ストリームの例外マスクに従って `ios_base::failure` 例外が送出されます。
//10 | `ios_base::failure` 例外を捕獲するのは、 元の例外を送出するためです。
//11 | 元の例外を再送出します。これまでに例外が発生していない場合は、ストリームの `setstate()` 関数でストリームのエラー状態を設定します。

挿入子も同じパターンで実装します。

>     template<class charT, class Traits>
>     basic_ostream<charT, Traits>& operator <<
>      (basic_ostream<charT, Traits >& os, const date& dat)
>     {
>      ios_base::iostate err = 0;

>      try {
>       typename basic_ostream<charT, Traits>::sentry opfx(os);

>       if(opfx)
>       {
>        char patt[3] = "%x";
>        charT fmt[3];
>        use_facet<ctype<charT> >(os.getloc())
>           .widen(patt,patt+2,fmt);                                //1
>        if (
>        use_facet<time_put<charT,ostreambuf_反復子<charT,Traits> > >
>          (os.getloc())
>          .put(os,os,os.fill(),&dat.tm_date,fmt,(fmt+2))           //2
>          .failed()                                                //3
>        )
>           err = ios_base::badbit;                                 //4
>        os.width(0);                                               //5
>       }
>      } //実行
>      catch(...)
>      {
>         bool flag = FALSE;
>         try {
>                os.setstate(ios_base::failbit);
>             }
>         catch( ios_base::failure ) { flag= TRUE; }
>         if ( flag ) throw;
>      }

>      if ( err ) os.setstate(err);

>      return os;
>     }

挿入子と抽出子には、大きな違いはありません。

//1 | ロケールの `time_put` ファセットにある `put()` 関数を使用します。融通性があり、1 つだけでなく複数の書式指示子を指定することができます。書式シーケンス指示子をおさめた文字配列を宣言し、必要であればワイド文字に拡張します。
//2 | ここで書式指示子を `time_put` ファセットの `put()` 関数に与えます。
//3 | `put()` 関数は、生成された最後の文字の直後を示す反復子を返します。反復子 `failed()` 関数を呼び出して、先の出力が正しいかどうかを確認します。
//4 | 出力が失敗するのは、おそらくストリームが壊れている場合なので、`badbit` を設定します。
//5 | ここでフィールド幅をリセットします。ファセットの `put()` 関数でストリームの書式設定を使用し、各フィールド幅にもとづいて出力を調節するためです。使用後はフィールド幅をリセットするのが原則です。

### 11.5.2 まとめ

挿入子や抽出子の実装は、なぜ複雑に見えるのでしょう。なぜ最初の単純な方法ではいけないのでしょうか。

まず、実際には、この実装はあまり複雑ではありません (このことについては次の節で説明します)。次に、ほとんどの組み込み型のデータメンバーがユーザー定義型で構成され、実行時の効率がそれほど重要でない場合には、最初のアプローチにおける単純な抽出子と挿入子で十分だということも留意しておいてください。

ただし、入出力演算における実行時の効率性を重視する場合は、ストリームバッファに直接アクセスすることを推奨します。この場合は、高速の低レベルサービスを使用しますが、低レベルサービスでは行わない書式制御やエラー処理などを追加して行う必要があります。このマニュアルで取り上げる例では、最適なパフォーマンスを目指しています。ファセットが直接ストリームバッファにアクセスするため、日付のロケール依存の構文解析と書式設定の抽出子と挿入子は非常に効率性が高くなっています。いずれにしても、これまでに紹介してきたパターンを使用してください。

Copyright (c) 1998, Rogue Wave Software, Inc.
このマニュアルに関する誤りのご指摘やご質問は、電子メールにてお送りください。

OEM リリース, 1998 年 6 月