## 12.3 パラメータ付きマニピュレータ

パラメータ付きのマニピュレータでは、考慮すべき点が増えるため、パラメータのないマニピュレータよりも複雑です。これらの点を詳しく説明し、パラメータ付きマニピュレータのさまざまな実装方法を検討する前に、1 つ例を挙げます。これは、`setprecision()` や `setw()` などの標準マニピュレータを実装する手法です。

### 12.3.1 標準マニピュレータ

Rogue Wave の標準入出力ストリームの実装では、パラメータ付きマニピュレータのほとんどを、ある 1 つの手法で実装しています。マニピュレータ型 `manipT` は、関数ポインタ型です。マニピュレータオブジェクトはポイントされる関数です。付属関数 `fmanipT` は 大域関数です。

C++ 標準では、マニピュレータ型を `smanip` と定義しています。itself 型は実装側の定義なので、これを返す標準マニピュレータがあること以外は公開されていません。Rogue Wave の実装で、`smanip` はクラステンプレートです。

>     template<class T>
>     class smanip {
>       public:
>         smanip(ios_base& (*pf)(ios_base&, T), T manarg);
>     };

`setprecision()` のような標準マニピュレータは、大域関数として実装することができ、`smanip<T>` 型のオブジェクトを返します。

>     inline smanip<int> setprecision(int n)
>     { return smanip<int>(sprec, n); }

付属関数 `fmanipT` は大域関数 `sprec` です。

>     inline ios_base& sprec(ios_base& str, int n)
>     {
>       str.precision(n);
>       return str;
>     }

### 12.3.2 パラメータ付きマニピュレータの原則

前節では、1 つのパラメータのマニピュレータの実装例によって、入出力ストリームにおける標準マニピュレータの実装手法を紹介しました。これ以外にも、パラメータ付きマニピュレータを実装する方法はあります。この節では、その他の手法を紹介します。いずれの方法でも、1 つのパラメータのマニピュレータについて説明していますが、複数のパラメータがあるマニピュレータにも簡単に応用することができなす。

最初は、順序を追って説明します。まず、パラメータ付きマニピュレータとは何であるかを説明します。

パラメータ付きマニピュレータは、次のような式のストリームに対して挿入や抽出することができるオブジェクトのことです。

>     cout << Manip(x);
>     cin  >> Manip(x);

`Manip(x)` は、`manipT` 型のオブジェクトであり、これにシフト演算子を多重定義します。対応する挿入子の例を次に示します。

>     template <class charT, class Traits>
>     basic ostream<charT,Traits>&
>     operator<< (basic ostream<charT,Traits>& ostr
>                ,const manipT& manip)
>     {  // 付属関数 fmanipT を呼び出す。例は以下のとおり。
>        (*manip.fmanipT )(ostr,manip.argf);
>        return os;
>     }

ここで定義された挿入子では、式 `cout << Manip(x);` は、上記のシフト演算子、`operator<<(cout, Manip(x))` の呼び出しと同じはたらきをします。

付属関数 `fmanipT` で副作用が作成される場合、マニピュレータでは個別に引数で指定して付属関数を呼び出す必要があります。そのため、シフト演算子内からの呼び出しに使用する引数とともに、付属関数を保存しておく必要があります。

付属関数 `fmanipT` は、静的関数か大域関数、または `manipT` 型などのメンバー関数です。

上記の挿入子では、付属関数 `fmanipT` は静的関数か大域関数であるものとし、引数は 1 つだけとします。一般に、マニピュレータ型 `manipT` は次のようになります。

>     template <class FctPtr, class Arg1, class Arg2, >
>     class manipT
>     {
>      public:
>        manipT(FctPtr, Arg1, Arg2, );
>      private:
>        FctPtr fp_;
>        Arg1   arg1_;
>        Arg2   arg2_;
>     };

ただし、これはマニピュレータの単なる例でしかありません。付属する副作用関数とその引数が、マニピュレータ型の個々のシフト演算子内から呼び出せるものであれば、マニピュレータ型は基本的にどのようにでも定義することができます。この章の後半では、たとえばi、 C++ 標準で定義している `smanip` などのマニピュレータ型の例も紹介します。これは、標準マニピュレータが返す実装定義の関数型です。詳細は、『標準 C++ クラスライブラリ・リファレンス』を参照してください。

上の例では、多重定義されたシフト演算子に対する引数として与えられたマニピュレータオブジェクトは、`Manip(x)` で得ることができます。その方法は、次の 3 通りが考えられます。

1. `Manip(x)` は関数呼び出しです。この例で `Manip` は `X` 型の引数をとる関数の名前であり、 `manipT` 型のマニピュレータオブジェクトを返します。すなわち、`Manip` は次の識別形式を持つ関数です。

       manipT Manip (X x);

2. `Manip(x)` は、コンストラクタ呼び出しです。この場合、`Manip` は、`X` 型の引数をとるコンストラクタのクラスの名前であり、`Manip` 型のマニピュレータオブジェクトを作成します。すなわち、`Manip` と `manipT` は同じものです。

       class Manip {
        public:
          Manip(X x);
       };

3. `Manip(x)` は、関数オブジェクトに対する呼び出しです。この場合、`Manip` は、`M` クラスのオブジェクトであり、`X` 型の引数をとる関数呼び出し演算子を定義し、`manipT` 型のマニピュレータオブジェクトを返します。

       class M {
        public:
          manipT operator()(X x);
       } Manip;

(1) と (2) の解法は、意味論的には (3) の方法と異なります。(1) では、`Manip` は関数であり、ユーザーが作成する必要はありません。(2) では、`Manip` はクラス名であり、名前なし一時オブジェクトがマニピュレータオブジェクトのはたらきをします。しかし (3) の `Manip` は、ユーザーが明示的に作成するマニピュレータオブジェクトです。したがって、ユーザーは次のコードを書く必要があります。

>     manipT Manip;

>     cout << Manip(x);

この方法は少し不便です。ユーザーはもう 1 つの名前、すなわちマニピュレータ型 `manipT` を理解している必要があり、また、マニピュレータオブジェクト `Manip` を作成する必要があるためです。16 このような理由から、(3) の解法はマニピュレータに状態がある場合に適しています。つまり、挿入のたびに次の行番号を設定するような (ここでは `lineno` という名前にします)、追加データを格納するマニピュレータです。

(2) の方法で問題となるのは、名前のないオブジェクトを処理できないコンパイラがあることです。

以上 3 つの方法は、付属関数を次の中から選択することができます。付属関数`fmanipT` として次のいずれかを選択してください。

1. 静的関数または大域関数
2. 静的メンバー関数
3. 仮想メンバー関数

これら 3 つの選択肢のうち、マニピュレータとその付属関数をカプセル化することができるため、オブジェクト指向プログラムには (b) の静的メンバー関数の利用が適しています。特に (3) のように、マニピュレータに状態があって、マニピュレータが関数オブジェクトであり、付属関数でマニピュレータの状態にアクセスする必要がある場合に最適です。

(c) の仮想メンバー関数を使用する方法では、マニピュレータの挿入や抽出のたびに仮想関数呼び出しのオーバーヘッドがあります。マニピュレータに状態があり、その状態を付属マニピュレータ関数で変更する必要がある場合に適した方法です。静的メンバー関数では、マニピュレータの静的データにしかアクセスすることができません。しかし、非静的メンバー関数では、オブジェクト固有のデータにアクセスすることができます。

### 12.3.3 パラメータ付きマニピュレータの例

この節では、パラメータ付きマニピュレータの例をいくつか紹介します。例は、マニピュレータ型の (1) から (3) の解法と、付属関数の (a) から (c) の方法を適当に組み合わせたものです。また、標準マニピュレータ `setprecision()` を使用したさまざまな手法を紹介します。

例 1: 関数ポインタと大域関数。この例では、(1) と (c) を組み合わせています。したがって、次のようになります。

- `manipT` は、関数ポインタ型です。
- マニピュレータオブジェクトは、ポイント先の関数です。
- 付属関数 `fmanipT` は、大域関数です。

標準入出力ストリームの Rogue Wave の実装では、ほとんどのパラメータ付きマニピュレータを上記の方法で実装しています。第 7.3.2 も参照してください。

例 2: 名前なしオブジェクトと静的メンバー関数。この例では、(2) と (b) を組み合わせています。したがって、次のようになります。

- マニピュレータオブジェクト `Manip` は名前なしオブジェクトです。
- マニピュレータ型 `manipT` はクラスです。
- 付属関数 `fmanipT` は、静的メンバー関数です。

マニピュレータ型 `manipT` は、入出力ストリームで定義したマニピュレータ型 `smanip` から派生させることができます。次に、`setprecision()` のようなマニピュレータの別の実装方法を示します。

>     class setprecision : public smanip<int> {
>       public:
>         setprecision(int n) : smanip<int>(sprec_, n) { }
>       private:
>         static ios_base& sprec_(ios_base& str, int n)
>         { str.precision(n);
>           return str;
>         }
>     };

例 3: 名前なしオブジェクトと仮想メンバー関数。この例では、(2) と (c) を組み合わせています。したがって、次のようになります。

- マニピュレータオブジェクト `Manip` は名前なしオブジェクトです。
- マニピュレータ型 `manipT` はクラスです。
- 付属関数 `fmanipT` は、クラスの仮想メンバー関数です。

ここでは、付属関数 `fmanipT` は、マニピュレータ型 `manipT` の非静的メンバー関数です。このようなモデルでは、マニピュレータには付属関数 `fmanipT` までのポインタは格納されませんが、付属関数は純粋な仮想メンバー関数として定義されます。したがって、マニピュレータ型 `manipT` は抽象クラスとなり、具象マニピュレータ型は、その抽象マニピュレータ型から派生させることができます。付属関数を表す仮想メンバー関数は、これらを利用して実装します。

標準マニピュレータ型 `smanip` は、実装定義のマニピュレータ型のため、 明らかに新しいマニピュレータ型が必要になります。Rogue Waveの標準 C++ ライブラリでは、仮想メンバー関数ではなく、付属関数までのポインタが格納されています。次に、必要な抽象マニピュレータ型を示します。

>     template <class Arg, class Ostream>
>     class virtsmanip
>     {
>      public:
>       typedef Arg argument_type;
>       typedef Ostream ostream_type;
>       virtsmanip (Arg a) : arg_(a) { }

>      protected:
>       virtual Ostream& fct_(Ostream&,Arg) const = 0;
>       Arg arg_;

>       friend Ostream&
>       operator<< (Ostream& ostr
>                  ,const virtsmanip<Arg,Ostream>& manip);
>     };

`virtsmanip` 型は、次の点で標準型 `smanip` とは異なります。

- `virtsmanip` 型は、前述のように純粋な仮想メンバー関数 `fct_()` を定義します。
- 引数 `arg_` と仮想関数 `fct_()` は保護メンバーなので、マニピュレータ型の各シフト演算子はフレンド関数になります。
- `virtsmanip` 型は、出力マニピュレータだけの基底クラスです。

標準マニピュレータ `smanip` では、`ios_base` での参照のために関数に対するポインタを要求します。この方法では、意図するかしないかにかかわらず、マニピュレータは常に入力ストリームおよび出力ストリームに適用されます。新しいマニピュレータ型 `virtsmanip` では、入力ストリームに不用意に適用されることのないマニピュレータを定義することができます。

新しいマニピュレータ型ができたので、マニピュレータ挿入子の新しい多重定義バージョンも必要です。

>     template <class Arg, class Ostream>
>     Ostream&
>     operator<< (Ostream& ostr, const virtsmanip<Arg,Ostream>& manip)
>     {
>        manip.fct_(ostr,manip.arg_);
>        return ostr;
>     }

これらを前提として、次に `setprecision()` のようなさらに別の実装方法を紹介します。この場合、`setprecision()` は、出力ストリーム専用のマニピュレータです。

>     class setprecision : public virtsmanip<int,basic_ostream<char> >
>     {
>     public:
>      setprecision(argument_type n)
>      : virtsmanip<argument_type,ostream_type>(n) { }

>     protected:
>      ostream_type& fct_(ostream_type& str, argument_type n) const
>      {
>        str.precision(n);
>        return str;
>      }
>     };

例 4: 関数オブジェクトと静的メンバー関数。この例では、(3) と (b) を組み合わせています。したがって、次のようになります。

- マニピュレータオブジェクト `Manip` は、`M` 型のオブジェクトであり、関数呼び出し演算子を定義します。
- マニピュレータ型 `manipT` は、クラス型であり、クラス `M` の多重定義された関数呼び出し演算子で返ります。
- 付属関数 `fmanipT` は、クラス `M` の静的メンバー関数です。

関数オブジェクトをマニピュレータとして使用するこの解法は、マニピュレータオブジェクトに状態があるという点で先の方法と意味論的に異なります。続けて使用する場合もデータを保存することができます。

別の例を利用して、この手法を紹介します。それは、マニピュレータオブジェクトが管理する一定の文字列を挿入する出力マニピュレータです。このようなマニピュレータには、各行に接頭辞を挿入するなどの用途があります。

>     Tag<char> change_mark("v1.2 >> ");

>     while ( new_text )
>        ostr << change_mark << next_line;

>     change_mark("");
>     while ( old_text)
>        ostr << change_mark << next_line;

ここでは、標準マニピュレータ `smanip` から `Tag` マニピュレータを派生させています。`smanip` の付属関数には、`ios_base` 参照をパラメータに持つという制約があります。この例では、保存テキストをストリームに挿入するため、ストリームの挿入子が必要になります。ただし、`ios_base` には挿入子や抽出子はありません。したがって、`smanip` と同様に、出力ストリームに対する参照をとる付属関数が可能な、新しいマニピュレータ基底型が必要になります。

>     template <class Ostream, class Arg>
>     class osmanip {
>       public:
>         typedef Ostream ostream_type;
>         typedef Arg argument_type;

>         osmanip(Ostream& (*pf)(Ostream&, Arg), Arg arg)
>         : pf_(pf) , arg_(arg) { ; }

>       protected:
>         Ostream&     (*pf_)(Ostream&, Arg);
>         Arg          arg_;

>       friend Ostream&
>         operator<<
>         (Ostream& ostr, const osmanip<Ostream,Arg>& manip);
>     };

次に、新しいマニピュレータ型 `osmanip` の挿入子を定義します。

>     template <class Ostream, class Arg>
>     Ostream&
>     operator<< (Ostream& ostr,const osmanip<Ostream,Arg>& manip)
>     {
>        (*manip.pf_)(ostr,manip.arg_);
>        return ostr;
>     }

関数オブジェクト型 `M` を定義します。ここでは `Tag` と呼びます。

>     template <class  charT>
>     class Tag
>     : public osmanip<basic_ostream<charT>, basic_string<charT> >
>     {
>     public:
>      Tag(argument_type a = "")
>       : osmanip<basic_ostream<charT>, basic_string<charT> >
>         (fct_, a) { }

>      osmanip<ostream_type,argument_type>&
>       operator() (argument_type a)
>       {
>         arg_ = a;
>         return *this;
>       }

>     private:
>      static ostream_type& fct_ (ostream_type& str, argument_type a)
>      {
>        return str << a;
>      }
>     };

このマニピュレータ型の意味論は、先の意味論や標準マニピュレータ `setprecision` とは異なります。次の例のように、このマニピュレータオブジェクトは、使用前に明示的に作成する必要があります。

>     Tag<char> change_mark("v1.2 >> ");

>     while ( new_text )
>        ostr << change_mark << next_line;

>     change_mark("");
>     while ( old_text)
>        ostr << change_mark << next_line;

この種のマニピュレータは、柔軟性に優れています。上の例では、マニピュレータの作成時にデフォルトテキストが `"v1.2 >> "` に設定されます。その後は、このマニピュレータをパラメータのないマニピュレータとして使用することで、このテキストが引き続き使用されます。これを、引数をとるマニピュレータとしても使用することができます。その場合は、挿入のたびに異なる引数を指定します。

例 5: 関数オブジェクトと仮想メンバー関数。先の例では、静的メンバー関数を付属関数として使用しました。この場合、付属関数でマニピュレータの状態を変更できないのが欠点となります。変更が必要な場合は、代わりに仮想メンバー関数を使用します。

この最後の例では、余分なデータを保存するマニピュレータを使用します。先に説明した `lineno` マニピュレータです。このマニピュレータは、挿入のたびに次の行番号を追加します。

>     LineNo lineno;
>     while (!cout)
>     {
>        cout << lineno << ;
>     }

このマニピュレータは、(3) と (b) パターンで実装します。

- マニピュレータオブジェクト `Manip` は、`M` 型のオブジェクトであり、関数呼び出し演算子を定義します。
- マニピュレータ型 `manipT` は、クラス型であり、クラス `M` の多重定義された関数呼び出し演算子で返ります。
- 付属関数 `fmanipT` は、クラス `M` の静的メンバー関数です。

マニピュレータオブジェクトには、マニピュレータオブジェクトが作成されるたびに初期化される行番号が組み込まれます。`lineno` マニピュレータを挿入するたびに、行番号が増分します。

マニピュレータ基底型の場合は、例 3 のマニピュレータ型 `osmanip` を少し変更したバージョンを使用します。この例の付属関数は、定数メンバー関数ではない可能性があるため、変更が必要です。

>     template <class Arg, class Ostream>
>     class virtsmanip
>     {
>      public:
>       typedef Arg argument_type;
>       typedef Ostream ostream_type;
>       virtsmanip (Arg a) : arg_(a) { }

>      protected:
>       virtual Ostream& fct_(Ostream&,Arg) = 0;
>       Arg arg_;

>       friend Ostream&
>       operator<< (Ostream& ostr
>                  ,virtsmanip<Arg,Ostream>& manip);
>     };

>     template <class Arg,class Ostream>
>     Ostream&
>     operator<< (Ostream& ostr
>                ,virtsmanip<Arg,Ostream>& manip)
>     {
>        manip.fct_(ostr,manip.arg_);
>        return ostr;
>     }

行番号マニピュレータは、次のように実装することができます。

>     template <class Ostream>
>     class LineNo
>      : public virtsmanip<int,Ostream >
>     {
>     public:
>      LineNo(argument_type n=0)
>       : virtsmanip<argument_type, ostream_type> (n)
>       { }

>      virtsmanip<argument_type,ostream_type>&
>       operator() (argument_type arg)
>       {
>        arg_ = arg;
>        return *this;
>       }
>     protected:
>      argument_type lno_;
>      ostream_type& fct_ (ostream_type& str, argument_type n)
>      {
>        lno_ = (n>0) ? n : lno_;
>        str << ++lno_;
>        arg_ = -1;
>        return str;
>      }
>     };

仮想メンバー関数を付属マニピュレータ関数にすると、マニピュレータを挿入するたびに、仮想関数呼び出しのオーバーヘッドが実行されます。挿入のたびにマニピュレータの状態を更新する必要がある場合は、静的メンバー関数は適していません。この場合は、マニピュレータ型のフレンドである大域関数が適しています。ただし、オブジェクト指向プログラムでは、フレンド先の非公開データメンバーや保護データメンバーを変更する大域変数は使用しないでください。

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

OEM リリース, 1998 年 6 月