木曜日, 1月 31, 2008

C++解説8(template)

今回はtemplateです。はっきり言ってこれには感動します。

1. templateとは
私自身がC++で最も素晴らしい機能と思っているのがtemplateです。
templateというのは直訳すれば「型」という意味です。

言葉の定義をする意味で「型」を辞書で引くと
(1)外見に現れたかたち。かっこう。《形》
(2)相対的な特性によって区別される性質や形態。タイプ。《型》
(3)同種類の物を幾つも作る時、基にする枠や紙。ひながた・鋳型・型紙など。《型》

このあたりで良いでしょう。
C++で言うtemplateとは(3)が一番しっくりくると思います。

template機能は「基にする枠」を作成するという機能になります。
枠ですから、それは関数かもしれませんし、クラスかもしれません。

具体的にする1段階前の抽象的な状態で枠を作成し
使用する時に具体化するという事になります。


2. 型が違うだけの処理はtemplateで書け!
C++で型というと「int」「char」「long」などがそれに該当します。
「型が違うだけの処理」とは「int、longという型だけが違う処理」を指します。

ここでMAXという関数を作成します。
とりあえず「int」という型で作成すると

int max(int lhs, int rhs) {
return (lhs >= rhs ? lhs : rhs);
}

こんな感じですね。
float、double、char、longなど様々な型においても
同じように作成する事が行えます。

少し、C言語に慣れている人であれば
#define MAX(X,Y) X >= Y ? X : Y

なんてマクロを作成するかもしれませんね。
しかし、マクロは型の安全性においてまったく頂けないため
C++の型の安全性という考え方にはまったく合わず
型毎に作成するなどはメンドクサクテ行えません。
そもそも、maxなんて単純な関数だから良いですが
複雑な関数だった場合、かなり不都合が生じる事でしょう。


templateはこのような問題を解決してくれる考え方、機能となります。

templateの文法を利用して上の関数を書き直します。

template
T max(T lhs, T rhs) {
return (lhs >= rhs ? lhs : rhs);
}

という事になります。
難しい事は一つもありません。
型の箇所を「T」という型で統一し、それを「template」と書く事で
template宣言している事を示しているのみです。
また、使う場合も以下の書き方となります。

int n1=1, n2=2, n3;
long l1=1, l2=2, l3;

n3=max(n1,n2);
l3=max(l1,l2);

n3=max(n1,n2);
l3=max(l1,l2);

cout << "n3 =" << n3 << endl;
cout << "l3 =" << l3 << endl;

これを見ると一目瞭然ですが
呼ぶ方は何も気にする必要がないという事です。
これで、templateから対応する型に対して関数が作成され、
実行時にそれが動作する事になります。


max関数を眺めると「>=」という演算子しか使用していませんから
「>=」だけ定義されていれば型が何であれ使用ができるという事になります。

これが最大限発揮するのは対象がユーザー定義型となった場合でしょう。

たとえば、以下のような動物クラスがあります。

#include
#include

using namespace std;

class CAnimal {
string sName;
long nSpeed;

public:
CAnimal(string InName, long InSpeed = 1) {
sName = InName;
nSpeed = InSpeed;
}

CAnimal() {
sName = "";
nSpeed = 0;
}

bool operator >=(CAnimal &rhs) {
return (this->nSpeed >= rhs.nSpeed ? true : false);
}

string GetName() {
return sName;
}

};

これは、名前と移動スピードを管理しているだけのクラスです。
さらに「>=」という演算子をオーバーロードしていて
移動スピードの速い、遅いで演算子を評価しています。
つまり、max関数にこの動物クラスを与えると
スピードの速い動物を特定する事になります。
それでは実際に使ってみます。

template
T max(T lhs, T rhs) {
return (lhs >= rhs ? lhs : rhs);
}

int main(void) {
CAnimal obj1("Lion", 10);
CAnimal obj2("Tiger",15);
CAnimal obj3;

obj3 = max(obj1, obj2);
cout << obj3.GetName() << endl;
}

この実行結果は「Tiger」となります。

このようにユーザー定義型にも、templateとは使用できるのが
非常に効果的で、効率的なプログラムを作成する事ができます。
これの詳しい話は「STL」のところが一番良いと思いますので
「STL」の方で詳細を書きたいと思います。

3. 真髄はこちらにある。クラステンプレート
関数に対して、クラスのテンプレートという事になります。

文法は関数の時と同じで、テンプレート化する型を前方で宣言をし
後はその記号を使用して書き進めるという事になります。

C言語の型をラップするクラスをテンプレートで作成してみます。

template
class Rapper {
T m;
public:
Rapper (T in=1) { m = in; }

void SetValue(T in=1) { m = in; }
void Add (T in=1) { m += in; }
void Subtract(T in=1) { m -= in; }

T GetValue () { return m; }
};

簡単すぎるラップクラスです。
値を直接書き換える事ができません。
基本的に加算、減算のみが行えます。

これは「int,long,float,double」などC++に存在する
プリミティブな型をラップする事ができます。

Rapper IntRapper(10);
Rapper LongRapper(10);
Rapper FloatRapper(10.1);
Rapper DoubleRapper(10.1);

IntRapper.Add();
IntRapper.Subtract(4);
IntRapper.GetValue();
こんな感じで使用します。


クラステンプレートそのものは特に難しい話ではありません。
また、テンプレート引数とは1つである必要もなければ
関数の引数を与えるように使用する事も可能です。

template
class Rapper {
T m[MAX];
int nPos;
public:
Rapper () { nPos = 0; }

void SetValue(T in) { m[nPos] = in; }
void Add () { nPos++; }
void Subtract() { nPos--; }

T GetValue () { return m[nPos]; }

void PrintMax() { cout << "MAX=" << MAX << "\n"; }
};

これは、配列の要素数MAXをテンプレート引数で与えられる事になります。
「= 10」が表しているのはデフォルト引数ですから
引数として何も与えなければ「10」がMAXという事になります。

Rapper IntRapper1;
Rapper IntRapper2;

IntRapper1.PrintMax();
IntRapper2.PrintMax();

この実行結果は
MAX=10
MAX=20
となります


4.templateの特殊化
templateには特殊化という考え方が存在します。
ある特定の条件において特別な処理を行うケースを指します。

「copy」という関数について考えます。

template
void copy(T *lhs, T *rhs) {
*lhs = *rhs;
}

int main(void) {
int i = 0,j = 1;
cout << "(i,j)=(" << i << "," << j << ")\n";
copy(&i,&j);
cout << "(i,j)=(" << i << "," << j << ")\n";

char a[10], b[10];
memset(a, 0, 10);
memset(b, 0, 10);
memcpy(b, "test", 4);
cout << "(a,b)=(" << a << "," << b << ")\n";
copy(a, b);
cout << "(a,b)=(" << a << "," << b << ")\n";
}

この実行は期待する通りにはいきません。
文字列のコピーは代入演算子(=)では処理ができないからです。
そこで、char型についてだけ特殊なコピーを用意する事にします。

template<>
void copy(char *lhs, char *rhs) {
memcpy(lhs, rhs, strlen(rhs));
}

これはtempalte関数コピーを「char」についてのみ特殊処理を記述した事になります。
これをtemplateの特殊化と言います。
この特殊関数が存在すると上のプログラムは期待通りに動作すると思います。


今回説明をしたtemplateとは、
今後登場するSTLの説明には知識として不可欠となります。
書くことでイメージがはっきりすると思いますので
プログラムを作成する事をお勧めします。

0 件のコメント: