その11 テンプレートの特殊化で特別扱いを
テンプレートはクラス内に型に依存しない部分を作るC++の標準機能です。おさらいとして、例えばオブジェクトを単純にnewする簡単なファクトリクラスを作ってみます:
template< class T >
class Factory {
public:
static T* create() {
return new T;
}
};
ctemplate<class T>とする事で、Factoryクラス内で「T型」という任意の型を扱う事ができます。この引数は「テンプレート引数」と呼ばれています。createメソッドはT型のオブジェクトをnewして、そのポインタを返しているだけです。
このクラス自体は実用性はあまり無いのですが、使ってみるとこういう感じです:
int main() {
int *pInt = Factory<int>.create();
double *pDbl = Factory<double>.create();
return 0;
}
Factory<[テンプレート型]>とする事でテンプレートの引数が確定します。よってFactory<int>::create()はint型のヒープメモリを、Factory<dpuble>::create()はdouble型のヒープを返してくれます。
@ テンプレートの特殊化とは?
テンプレートを使っている時にふとこう思うことがあります。「ほとんどすべての型についてはデフォルトで良いけど、ある型の時だけは別の振る舞いをしてほしい」と。例えば上のcreateメソッドで、意味は無いのですが、float型の時だけ常にNULLを返して欲しいとします。こういう時に真っ先に思いつくのが「継承と仮想関数」です。つまり、Factoryクラスを継承したFloatFactoryクラスを作って、createメソッドをオーバーライドしちゃえば…と考えるわけです。しかし、これはできません。
Factory::createメソッドはstaticで宣言されています。staticなメソッドは言語の仕様で仮想化できないんです。では、次のような実装はどうでしょうか?
class FloatFactory : public Factory<float> {
public:
static float* create() {
return NULL;
}
};
これは合法です。合法ですが…テンプレートの引数を固定したに過ぎず、
float *pFloat = Factory<float>.create();
と書くと普通に有効なfloat型のポインタが返るのは変わりません。やりたいのは、同じ宣言の仕方でNULLが返ってくる事なんです。
これを実現するには「テンプレートの特殊化」という特別な記述方法を用います。具体的には次のように書きます:
template<>
class Factory<float> {
public:
static float* create() {
return NULL;
}
};
template<>というは引数なしテンプレートを表します。注目はクラス宣言です。「Factory<float>」となっています。これがテンプレートの特殊化です。こう書くとこのクラス宣言はFactory<float>型の宣言とみなされます。後は同じcreateメソッドを実装するだけです。これで、
float *pFloat = Factory<float>.create();
とするとNULLが返ってきます。目標達成です。
「template<>って必要なの?」と思われるかもしれません。これは必要です。これが無いと「明示的な特殊化は 'テンプレート <>' を必要とします。」というコンパイラエラーが出ます。
A 一方の型だけを特殊化
今度はテンプレート引数が2つあるクラスの片方の型を特殊化したい場合です:
template<class T, class U>
class Add {
public:
T operator()( const T &arg0, const U &arg1 ) {
return (T)(arg0 + arg1);
}
};
2つの引数を足し算するテンプレートです。ここでも、意味は無いのですが、第2テンプレート引数の型がfloatの時に演算をしないように振る舞いを変えてみます:
template<class T>
class Add<T, float> {
public:
T operator()( const T &arg0, const float &arg1 ) {
return arg0;
}
};
今度はtemplate<class T>とテンプレート引数が1つ定義されています。そしてAdd<T, float>と第1テンプレート引数は任意、第2がfloat型の場合の特殊化である事が示されています。こういう感じで好きなテンプレート引数だけを特殊化する事ができるわけです。
B 特殊化はどこで使うのか?
テンプレートの特殊化は多種多様な場面で使われています。STLの中などは特殊化祭りだったりします。特殊化を具体的にどう使うかは、どういう実装が必要かによって様々だと思います。例えば冒頭で挙げたファクトリクラスを考えてみます。多くの型ではnewによって新しいオブジェクトを作れるかもしれません。しかし、ある特定のクラスでは「コンストラクタをprivate宣言してnewによる生成を禁止」している場合があります。こういう時には、独自のファクトリメソッドを提供している場合が多いです。その場合、例えば、
template<>
class Factory<MyClass> {
public:
static MyClass* create() {
return MyClass::create();
}
};
などのように独自のクラスのファクトリを内部で呼ぶような特殊化を行うと、Factoryクラスを統一的に扱えるようになります。元のテンプレートクラスを変えずに機能だけ変更できるのが嬉しい所ですね。
同じメソッドについて、特殊化によって「別の意味合いになる振る舞い」をさせるのはオブジェクト指向ではタブーです(ですから本章の例は駄目ですよ(^-^;)。メソッドの意味を変えずに特殊な振る舞いを実装し、テンプレートをより一般化させるのが特殊化の原則です。うまく使いたいものです。