実はテンプレート引数には型以外のものもとることができます。それは一体何なのでしょうか?
では、今回の要点です。
では、いってみましょう。
今回は疑似乱数生成のためのクラスを作りたいと思います。疑似乱数については第3部第31章を参考にして下さい。
第3部第31章では、乱数を作るために使う変数「乱数種」にはグローバル変数を使っていました。なので、乱数列は同時に1つしか作ることができません。いろいろ凝ったことをやっていると、それでは不便なことも出てきます。
ということで、一度に何個も乱数列を作れるようにクラス化してみましょう。この作業自体は第3部第31章のソースを改造するだけで簡単にできますね。
プログラム1 |
---|
// Rand.h #include <stddef.h> #include <time.h> // 使用する上位ビットの数 const int CRAND_BITS = 15; // 乱数の上限 const unsigned int CRAND_MAX = (1 << CRAND_BITS) - 1; class CRand { private: unsigned int m_nSeed; // 乱数種 public: // 初期化 void Init(unsigned int nSeed); CRand(unsigned int nSeed); // 現在の時間で初期化 void Init(); CRand(); // 乱数種の取得 unsigned int GetSeed(); // 疑似乱数生成 int Rand(); operator int(); operator unsigned int(); // [0, 1)(0以上1未満)の疑似乱数生成 double RandD(); operator double(); // ある数未満の乱数を生成 int Rand(int nLimit); }; // 初期化 inline void CRand::Init(unsigned int nSeed) { m_nSeed = nSeed; } inline CRand::CRand(unsigned int nSeed) { Init(nSeed); } // 現在の時間で初期化 inline void CRand::Init() { Init((unsigned int)time(NULL)); } inline CRand::CRand() { Init(); } // 乱数種の取得 inline unsigned int CRand::GetSeed() { return m_nSeed; } // 疑似乱数生成 inline CRand::operator int() { return Rand(); } inline CRand::operator unsigned int() { return Rand(); } // [0, 1)(0以上1未満)の疑似乱数生成 inline double CRand::RandD() { return (double)Rand() * (1.0 / (CRAND_MAX + 1)); } inline CRand::operator double() { return Rand(); } // ある数未満の乱数を生成 inline int CRand::Rand(int nLimit) { // % nLimit よりも上質の分布になります // ただし、16ビット機では long でやらないといけません return Rand() * nLimit / (CRAND_MAX + 1); } |
プログラム2 |
// Rand.cpp #include "Rand.h" // 疑似乱数生成 int CRand::Rand() { m_nSeed = m_nSeed * 48828125 + 1; // 上位ビットを返します return m_nSeed >> (32 - CRAND_BITS); } |
どうせクラスにするのならと、便利そうな機能を追加しておきました。CRand はこんなもんですね。
さて、ここでマニアックな処理をしたい人が「使用する上位ビットの数」「乱数生成に使う 48828125 と 1 の値」もいろいろ変えたいという要求を出してきましたとします。
それならとこれをメンバ変数にとってみると、「処理が遅くなる。定数にしろ」と言ってきます。
となると、定数を変えたクラスを別々に作らなければいけないのでしょうか? 答は半分YESで半分NOです。というのも、これはクラステンプレートを使って解決できるからです。
例えば CArray を考えると、CArray <int> と CArray <char> とは別々の実体が作られるけれども、プログラム自体は CArray というクラステンプレートを作るだけで構いません。これが半分YESで半分NOということです。
何はともあれ、テンプレート引数には型だけではなく整数定数も指定できるようにすることができるのです。例えば、1を指定したとき、2を指定したときなどで別々のクラスを作ることができるわけです。これは関数テンプレートでもできます。
どうやるかは簡単です。テンプレート引数に <型> <仮引数名> と加えてやればいいだけです。型には int か unsigned int が使えます。double など、小数は使えません。
template <unsigned int TMPL_BITS> class CRandTmpl { : :
そして、使うときはテンプレート引数を指定してやります。
CRandTmpl <10> rnd;
しかも、クラステンプレートならデフォルト引数も指定できます。実は、これは typename に対しても使えます。
template <unsigned int TMPL_BITS = 15> class CRandTmpl { : : CRandTmpl <> rnd; // テンプレート引数には15が指定されます // 山カッコは必要 template <typename TYPE = int> class CArray { : : CArray <> array;
ただし、関数テンプレートではデフォルト引数は使えません。
以上を踏まえて CRand を改造してみましょう。
プログラム |
---|
// Rand.h #include <stddef.h> #include <time.h> // unsigned は unsigned int と同等です template <unsigned TMPL_BITS = 15, unsigned TMPL_A = 48828125, unsigned TMPL_C = 1> class CRandTmpl { private: unsigned m_nSeed; // 乱数種 // 定数群 public: static const unsigned BITS; // 使用する上位ビットの数 static const unsigned MAX; // 乱数の上限 static const unsigned A; // 傾き static const unsigned C; // 切片 public: // 初期化 void Init(unsigned nSeed){ m_nSeed = nSeed; } CRandTmpl(unsigned nSeed){ Init(nSeed); } // 現在の時間で初期化 void Init(){ Init((unsigned)time(NULL)); } CRandTmpl(){ Init(); } // 乱数種の取得 unsigned GetSeed(){ return m_nSeed; } // 疑似乱数生成 int Rand(); operator int() { return Rand(); } operator unsigned(){ return Rand(); } // [0, 1)(0以上1未満)の疑似乱数生成 double RandD(); operator double(){ return RandD(); } // ある数未満の乱数を生成 int Rand(int nLimit); }; //////////////////////////////////////////////////////////////// // 定数群 // 使用する上位ビットの数 template <unsigned TMPL_BITS, unsigned TMPL_A, unsigned TMPL_C> const unsigned CRandTmpl <TMPL_BITS, TMPL_A, TMPL_C> :: BITS = TMPL_BITS; // 乱数の上限 template <unsigned TMPL_BITS, unsigned TMPL_A, unsigned TMPL_C> const unsigned CRandTmpl <TMPL_BITS, TMPL_A, TMPL_C> :: MAX = (1 << TMPL_BITS) - 1; // 傾き template <unsigned TMPL_BITS, unsigned TMPL_A, unsigned TMPL_C> const unsigned CRandTmpl <TMPL_BITS, TMPL_A, TMPL_C> :: A = TMPL_A; // 切片 template <unsigned TMPL_BITS, unsigned TMPL_A, unsigned TMPL_C> const unsigned CRandTmpl <TMPL_BITS, TMPL_A, TMPL_C> :: C = TMPL_C; //////////////////////////////////////////////////////////////// // メンバ関数の実装 // 疑似乱数生成 template <unsigned TMPL_BITS, unsigned TMPL_A, unsigned TMPL_C> int CRandTmpl <TMPL_BITS, TMPL_A, TMPL_C> :: Rand() { m_nSeed = m_nSeed * TMPL_A + TMPL_C; // 上位ビットを返します return m_nSeed >> (32 - TMPL_BITS); } // [0, 1)(0以上1未満)の疑似乱数生成 template <unsigned TMPL_BITS, unsigned TMPL_A, unsigned TMPL_C> inline double CRandTmpl <TMPL_BITS, TMPL_A, TMPL_C> :: RandD() { return (double)Rand() * (1.0 / (MAX + 1)); } // ある数未満の乱数を生成 template <unsigned TMPL_BITS, unsigned TMPL_A, unsigned TMPL_C> inline int CRandTmpl <TMPL_BITS, TMPL_A, TMPL_C> :: Rand(int nLimit) { // % nLimit よりも上質の分布になります // ただし、16ビット機では long でやらないといけません return Rand() * nLimit / (MAX + 1); } // 普通は CRand を使えば充分、という風にしておきます typedef CRandTmpl <> CRand; |
何というかかなりうるさいコードになりましたが、とりあえずこれでテンプレート化できました。これで注文を出した人も満足してくれることでしょう。
では、今回の要点です。
今回まででテンプレートについては大体話し終わりました。次回からはちょっと高度な話に入ろうと思います。それではさようなら。
Last update was done on 2000.12.14
この講座の著作権はロベールが保有しています