ホーム < ゲームつくろー! < クラス構築編 < 線形補間テンプレートクラスを作ろう!(コンテナ実装編)

区間補間テンプレートクラスを作ろう!(コンテナ実装編)


 前章『区間補間テンプレートクラスを作ろう!(仕様編)』で補間テンプレートの仕様を固めました。この章では核になるinterpolatingコンテナテンプレートを実装します。どういうコンセプトのテンプレートを作るかについて詳しくは上記リンクをご参照下さい。



@ メソッドの実装

 interpolatingコンテナはSTLと同様のメソッドであるpush_back、begin、endそしてclearメソッドを持たせることにします。それぞれのメソッドについて詳しく見ていくことにしましょう。


○ クラス定義

 まずはinterpolatingテンプレートクラスの宣言です。

interpolatingテンプレートクラス宣言
template<
   class VAL,
   class X = double,
   class W = double,
   class STRATEGY = CLinerInterp<VAL,X,W>
>
class interpolating{...}

 4つの型引数がありますが、内3つは初期型があります。

VALは補間したいデータの型です。任意の型が指定できます(演算子の定義が必要ですがそれは後述します)。
Xは補間距離の型です。初期型はdouble型ですが、floatでもかまいません。まぁ、それ以外はあまり触らない部分ですね。
Wは補間に必要な付加情報の型です。デフォルトはdouble型にしています。これは主に次のSTRATEGYが使用します。STRATEGYが必要とするクラスを定義する事になります。
STRATEGYは補間ストラテジ(補間方法を提供するクラス)型を渡します。デフォルトでは線形補間を行うCLineInterpが渡されています。これについては別章で説明致します。

 手っ取り早く線形補間をしたい方は最初の型だけを指定すれば良い仕組みになっています。



○ コンストラクタ

 コンストラクタではメンバ変数の初期化を行います。interpolatingは点の情報のコンテナですから、少なくとも格納されている点の数、点へのポインタの情報が必要になるでしょう。さて、ここで言う「点」ですが、これは(値、区間幅、付加情報)の3点セットになります。ここではその3点セットの構造体をtrioテンプレートとしてまとめましょう。

trioテンプレートクラス宣言
template< class VAL, class X=double, class W=double >
class trio
{
public:
   VAL _val;
   X _x;
   W _w;

   // コンストラクタ
   trio( VAL &val, X &x, W &w )
   {
      _val = val;
      _x = x;
      _w = w;
   }
};


テンプレートなのであらゆる型を保持できます。以後このtrioクラスが多用される事になります。interpolatingテンプレートはtrioオブジェクトの配列を保持します。

interpolating::interpolatingメソッド
typedef typename trio<VAL, X, W> TRI;   // 型再定義

public:
// コンストラクタ
interpolating()
{
   _Size = 0;
   _ppTrio = (TRI**)malloc( sizeof(TRI*) ); // trioポインタ格納分確保
   *_ppTrio = INTERPOLATING_ENDPTR;
   _pLast = _ppTrio;
}


ここはいろいろと説明が必要ですね。まずtypedef部分です。ここはtrioテンプレートの長たらしい宣言を簡略化させています。この型再定義は「interpolatingクラスの内部」に記述するのがポイントです。なぜならば、trioテンプレートにinterpolatingに渡した型引数名をそのまま譲渡できるためです。この宣言1つによって、interpolatingとtrioテンプレートが同じ型を扱う事を保証しているわけで、極めて大切な宣言なんです。ところで、

 typedef typename ...

という部分を見慣れない方もいらっしゃるのではないでしょうか?typenameというのは「後ろの宣言は変数名じゃなくて型ですよ」と明記する記述方法です。実はVisual Studio 2003辺りからこの明示が必須となりました。VC++6をお使いの方は抜かしてもwarning止まりかもしれませんが、移植性を考えてつけるようにしてください。

 コンストラクタ内のSize変数は、格納されているtrioオブジェクトの配列要素数です(size_t型)。_ppTrioはTRIポインタ型(trioテンプレート)配列の先頭ポインタです。ダブルポインタですから「ポインタを格納する配列」なわけです。「どうして値その物の配列じゃ無いのか?」と思われるかもしれません。これにはmalloc関数で確保したメモリに対するコピー時の問題が絡んだ深〜い理由があります。しかし、とってもくどい話しになり趣旨に反しますので、ここでは割愛させて頂きます。知りたい方は掲示板でご質問下さい。

 _Last変数はTRIポインタ配列の最後の要素を指します。絶対に必要な変数ではありませんが一応設けておきます。

 INTERPOLATING_ENDPTRというのは、終端、もしくは無意味を表すマクロでして、次のように定義します。

終端ポインタマクロ
#define INTERPOLATING_ENDPTR  ((TRI*)(-1))

32ビット環境の場合これは0xffffffffとなります。実質こういうアドレスは生じませんので、無意味なポインタを表すマクロとして使えます。NULLはゼロとしての意味合いを持つことがありますので、終端として使えないときがありますので、このような代替ポインタを定義しました。




○ push_backメソッド

 おなじみ値を登録する(リストの後ろに付加する)メソッドです。

interpolating::push_backメソッド
void push_back(VAL &val, X &x, W weight)
{
   // トリオを生成
   TRI *p = new TRI(val, x, weight);
   // 配列を1つ拡張
   AddAryMemory(1);
   // 追加した配列部分に値を格納
   Rewrite( _pLast, p );
}


 引数では点を構成する3要素(VAL、X、W)を渡しています。内部ではTRI型のメモリを動的に確保します。次にそのポインタを格納する配列を1つ伸ばして、ポインタを保存します。AddAryMemoryメソッド及びRewriteメソッドは次のような実装です:

interpolating::AddMemoryメソッド、Rewriteメソッド
protected:

void AddAryMemory( size_t Cont )
{
   if(Cont>0){
   // 指定の大きさにポインタ配列を拡張
   _ppTrio = (TRI**)realloc( _ppTrio, sizeof(TRI*)*(_Size+Cont) );
   // 拡張した部分の値を初期化
   size_t i;
   for(i=0; i<Cont; i++)
   _ppTrio[_Size+i] = INTERPOLATING_ENDPTR;
   // サイズを変更
   _Size += Cont;
   // 最終ポインタ位置の設定を変更
   _pLast = _ppTrio+_Size-1;
   }
}

// ポインタの上書き
void Rewrite( TRI** pPlace, TRI *val )
{
   // 上書き部分のポインタ先を削除
   if(*pPlace != INTERPOLATING_ENDPTR)
      delete *pPlace;
   *pPlace = val;
}


 AddMemoryメソッドは引数の大きさ分だけ現在の配列を伸張させます。これにはrealloc関数が最適です。伸張後、増やした部分をとりあえずINTERPOLATING_ENDPTRマクロで初期化して、サイズと最終ポインタ位置(_pLast)を変更しています。これでポインタを格納する準備が整います。

 次のRewriteメソッドは、pPlaceが指す部分にvalポインタを上書きします。この時pPlaceに元々あった値は削除してしまいます。この2つのメソッドによって、push_backメソッドはうまく働きます。




○ beginメソッド

 interpolatingコンテナに格納された配列を巡る先頭イテレータを返すメソッドです。これもlistなどでおなじみですよね。

interpolating::beginメソッド
iterator begin()
{
   return iterator(_ppTrio, this);
}


非常に短い実装です。iteratorというクラスの引数付きコンストラクタを生成して戻しています。このiteratorテンプレートクラスについては、次の章で取り上げますので、neginメソッドの実装はこの程度だと今はお考え下さい。



○ endメソッド

 endメソッドは「終端イテレータ」を返すメソッドです。終端イテレータというのは、その名の通り終端を表すイテレータで、イテレーション(反復)の終わりを検知するために使われます。

interpolating::endメソッド
iterator end()
{
   return iterator(NULL, this);
}

 終端イテレータについてもイテレータの章で説明します。interpolatingテンプレートクラスはあくまでも終端イテレータを返すと言う仕事をこなすだけです。



○ clearメソッド

 TRIポインタ配列及びその先にあるVALを全部消去します。やる事は単純です。

interpolating::clearメソッド
// クリア
void clear()
{
   // 確保したメモリを全てクリアする
   size_t i;
   for(i=0; i<_Size; i++)
   {
      delete _ppTrio[i];
   }
   _Size = 0;
   _ppTrio = (TRI**)realloc( _ppTrio, sizeof(TRI*) );
   *_ppTrio = INTERPOLATING_ENDPTR;
   _pLast = _ppTrio;
}

 _ppTrioはポインタを格納しているので、上のように[ ]演算子でdeleteします。ただし、_ppTrio自体は削除せずに、reallocによって縮めます。あとはコンストラクタでの初期化と同様にするだけです。




 さて、以上でinterpolatingコンテナの実装は殆どおしまいです。コンテナとしての仕事しかしていないので案外簡単ですよね。interpolatingテンプレートクラスの全コードは、iterator及び補間ストラテジテンプレートの説明の後に公開する予定です。