株式会社エス・スリー・フォー

11.10 標準ライブラリなしでテンプレートを使用する場合

RWTValVector<T>RWTPtrVector<T>RWTIsvSlist<T>、および RWTIsvDlist<T> など、Tools.h++ テンプレートのいくつかは、標準 C++ ライブラリをもとにしていません。認定済みプラットフォームならばどれでも、これらのテンプレートを使用できます。「はじめに」で説明したように、いわゆる標準ライブラリベースのテンプレートの多くは、完全なインタフェースのサブセットに従うかぎり、標準 C++ ライブラリなしで使用することができます。

11.10.1 移植のために標準 C++ ライブラリに留意する場合

制約付きのサブセットインタフェースは、それに対応する標準ライブラリベースのインタフェースと、ほとんど完全に上位互換性があります。主な違いは、コレクションがどの実装を使用しているかにより、コレクションの中には異なる数のテンプレートパラメータをとるものがあるということです。例えば、RWTPtrHashSet を標準 C++ ライブラリと共に使用する場合、これは前の節で説明されているように 3 つの引数をとります。ところが、同じクラスを標準 C++ ライブラリなしで使用する場合、制約付きインタフェースはただ 1 つのテンプレートパラメータ、すなわち含まれる項目の型を要求します。標準 C++ ライブラリの有無にかかわらず動作する移植可能なコードを書くために、Tools.h++ には次の 2 つのマクロが用意されています。

  1. 最初のマクロ RWDefHArgs(T) は Rogue Wave Default Hash Arguments の略で、ハッシュベースのテンプレートコレクションに使用します。例えば、以下のように宣言することにより、

    RWTPtrHashSet<int, RWDefHArgs(int)> hset;

    標準 C++ ライブラリの有無にかかわらず同じ意味論を持つハッシュベースのセットを宣言します。項目型とマクロの間には、コンマを使用してはいけないことに注意してください。標準 C++ ライブラリがない場合、マクロは何も拡張せず、あたかも以下のように宣言されたかのようになります。

    RWTPtrHashSet<int> hset;

    ところが、標準 C++ ライブラリが使用可能になると、マクロが以下のように拡張されます。

    RWTPtrHashSet<int ,RWTHasher<int>, equal_to<int> > hset;

    この宣言は、3 つのパラメータすべてにおいて標準ライブラリベースのインタフェースの必要条件を完全に満たし、別の標準ライブラリベースでない実装と一致するように意味論を保ちます。

  2. 2 番目のマクロ RWDefCArgs(T) は、最初のマクロと似ています。Rogue Wave Default Comparison Arguments の略である RWDefCArgs(T) は、RWTPtrSortedVector および RWTValSortedVector と共用するためにあります。例:

    RWTValSortedVector<int RWDefCArgs(int)> srtvec;

    は、標準 C++ ライブラリの有無にかかわらず動作する移植可能な宣言です。この場合も、項目の型をマクロと区切るためにコンマを使用しないでください。

11.10.2 例

標準 C++ ライブラリをもとにしていないクラスの 1 つである、RWTValVector<T> を使う簡単な例をみてみましょう。

#include <rw/tvvector.h>                             // 1

main() {
    RWTValVector<double> vec(20, 0.0);                     // 2

    int i;
    for (i=0; i<10; i++)     vec[i] = 1.0;                 // 3
    for (i=11; i<20; i++)    vec(i) = 2.0;                 // 4

    vec.reshape(30);                                           // 5
    for (i=21; i<30; i++)    vec[i] = 3.0;                 // 6
    return 0;
    }

以下にプログラムを行ごとに説明します。

//1
ここで、RWTValVector<T> のテンプレートが定義されています。
//2
20 項目長で 0.0 に初期化された倍精度のベクトルが宣言され、定義されています。
//3
ベクトルの最初の 10 個の項目が 1.0 に設定されます。ここでは、RWValVector<double>::operator[](int) が使用されています。この演算子は常に引数上で境界チェックを実行します。
//4
ベクトルの次の 10 個の項目が 2.0 に設定されます。この場合は、RWValVector<double>::operator()(int) が使用されました。この演算子は一般に境界チェックを実行しません。
//5
メンバ関数 reshape(int) はベクトルの長さを変更します。
//6
最終的に、最後の 10 個の項目が 3.0 に初期化されます。

11.10.3 例

2 番目の例では、ハッシュ辞書を使用します。ハッシュ辞書を宣言するときにマクロ RWDefHArgs(T) を使うことにより、標準 C++ ライブラリにアクセスできるかどうかにかかわらず、コードが間違いなく移植できるようになります。

#include <rw/tvhdict.h>
#include <rw/cstring.h>
#include <rw/rstream.h>
#include <iomanip.h>

class Count {                                                  // 1
  int  N;
public:
  Count() :    N(0) { }                                        // 2
  int  operator++()  { return ++N; }                           // 3
  operator   int() { return N; }                               // 4
};

unsigned hashString ( const RWCString& str )                   // 5
  { return str.hash(); }

main() {

  RWTValHashDictionary<RWCString,
                       Count  /* 注意: ここにはコンマを入れないこと! */
                       RWDefHArgs(RWCString)> hmap(hashString); //6

  RWCString token;
  while ( cin >> token )                                       // 7
    ++hmap[token];                                             // 8

  RWTValHashDictionaryIterator<RWCString,Count> next(hmap);    // 9

  cout.setf(ios::left, ios::adjustfield);                     // 10
  while ( ++next )                                            // 11
    cout << setw(20) << next.key()
         << " " << setw(10) << next.value() << endl;          // 12

  return 0;
}

入力:
How much wood could a woodchuck chuck if a woodchuck could chuck wood ?

出力:
much                 1
wood                 2
a                    2
if                   1
woodchuck            2
could                2
chuck                2
How                  1
?                    1

このコードでの問題は、入力ファイルを読み取り、それを空白類によって区切られたトークンに分解し、各トークンの個数を数えて、結果を出力することです。辞書を使って各トークンをそれぞれのカウンタにマップする方が、より一般的でしょう。次に、このコードを行ごとに説明します。

//1
これは、辞書の値の部分として使用されるクラスです。
//2
カウンタをゼロにするデフォルトコンストラクタが用意されます。
//3
前置インクリメント演算子を与えます。これは、便利な方法でカウンタをインクリメントするために使用されます。
//4
Count を int に変換する変換演算子が用意されます。これを使用して、結果を印刷します。または、多重定義の operator<<() を使用して Count にそれ自体を印刷する方法を指定することもできます。ただし、型変換演算子の方が簡単です。
//5
これは、辞書コンストラクタに指定しなければならない関数です。この関数の役目は、キーの型の引数で与えられたハッシュ値を返すことです。Tools.h++ では、RWCStringRWDateRWTimeRWWString の各クラスに、ユーザ指定の関数の代わりに使用できる静的 hash メンバ関数が用意されています。例を一般的にしておくために、Tools.h++ によって定義されている静的 hash メンバ関数のどれかではなく、ユーザ定義関数を使用することにしました。
//6

ここで辞書が生成されます。キーを指定すると、辞書を使って値を調べることができます。この場合は、キーは型 RWCString で、値は型 Count になります。コンストラクタには、唯一の引数である、キーを与えるとハッシュ値を返す関数へのポインタが必要です。この関数は、前記の // 5 で定義しました。RWDefHArgs(T) マクロを使って、標準 C++ ライブラリの有無にかかわらず、異なるプラットフォーム間でプログラムが確実に移植できるようにしました。

//7
トークンは、入力ストリームから RWCString に読み込まれます。これは EOF に到達するまで継続されます。これはどのように行われるのでしょうか? 式 cin >> token は単一トークンを読み取り、ostream& を返します。クラス ostream は void* への型変換演算子を持ち、while ループによって実際に条件判定されています。ストリームの状態が「正常」であれば、Operator void* は、this を返します。それ以外の状態の場合は、ゼロを返します。EOF によりストリームの状態が「正常」でなくなるため、EOF に到達すると while ループが終了します。『Tools.h++ Class Reference』でRWCStringの項目を、コンパイラに付属のクラスリファレンスガイドで「ios」の項目をそれぞれ参照してください。
//8

ここですべての重要な処理が行われます。オブジェクト map は辞書です。これは、多重定義されている operator[] を持ちます。この演算子はキーの型の引数をとり、それに対応する値への参照を返します。値の型は、Count であることを思い出してください。すなわち、map[token] は型 Count になります。// 3 でみたように、Count は多重定義されている前置インクリメント演算子を持ちます。これは Count 上で呼び出され、それによって値が増加します。

キーが辞書に無い場合はどうなるのでしょうか? 多重定義されている operator[] が、値のクラスのデフォルトコンストラクタを使って作成した新しい値とともにキーを挿入します。これは、カウンタをゼロに初期化するように // 2 で定義されています。

//9
結果を印刷するときがきました。辞書を一掃して各キーと値を返す、反復子を定義することにより開始します。
//10
見た目がきれいなように、出力ストリームのフィールド幅を調整します。
//11
反復子は、コレクションクラスの最後に到達するまで前進します。すべてのテンプレート反復子で、前置インクリメント演算子が反復子を前進し、それがコレクションクラスの最後を超えていないかどうかを調べます。
//12
反復子の位置にあるキーと値が印刷されます。