ついに、Java にも「総称」(genericity、C++ で言うところの template) の機能が 追加されるらしい (参照記事)。 ついでに assert 機能も。これで、C++ の Java に対する優位は、 ほとんど無くなったのかな? そろそろ Java も視野に入れたほがよいのかな? 「って、お前、それは世間から2周遅れくらいだよっ!」 というようなツッコミが入りそうだが、正直なところ、
という 2つの条件を付けると、なかなか Java には移行できないんだよぉ。
まあ、それはさておき。
ときどき、ほとんど同じなのだけど、微妙に異なる振る舞いをする 2つのクラスが欲しくなることがある。今日、私が作りたくなったのは、 次のようなクラスであった。
class A {
public:
A *foo() {
// 処理処理…
}
};
class B {
public:
B *foo() {
// 処理処理…
}
};
自分自身へのポインタを返すことを除けば、メソッド foo() の中味は、ほとんど一緒。 となれば、普通は、 ベースになるクラスを作って foo() を仮想関数で定義し、A や B は、 そのベースクラスから派生させることになるだろう。 しかし、今回は故あって、vtblポインタをオブジェクトに持ちこみたくなかった。
次の手は、テンプレートを使ってクラス定義をまとめることである。 上の定義をそのままテンプレート化すると、以下のようになろうかと思う。
template<class T> class X {
public:
X *foo() {
// 処理処理…
}
};
だが、ここで 「あれ? パラメータ T って何?」 ということになるんである。 私がやりたいのは、X そのものが A なり B なりのクラスになることであって、 パラメータの T にはとくに意味がないのだ。
ここで一瞬考えたのは、次のような定義だ。
template<class T> class X {
public:
T *foo() {
// 処理処理…
}
};
typedef X <A> A;
もちろん、こんなことは不可能だ。typedef のところで、
実引数として A を使った時点で、「A が未定義」 というエラーになる。
そこでまた、数瞬考えた。ようするに、
あらかじめクラス A が定義ないし宣言されていればよいわけだから、
まず、「class A;」 と宣言してしまおう。
それに、X<A> と A が同一である必要はなく、
A が X<A> と同じ振る舞いをしてくれればよいだけなのだから、後で、
「class A : public X<A> { };」 で定義しよう。次のような定義だ。
class A;
template<class T> class X {
public:
T *foo() {
// 処理処理…
}
};
class A : public X <A> { };
で、さらによくよく考えたら、先頭の 「class A;」 の宣言が無くたって、
最初に A を使っているのは、「class A : …」 の後じゃないか。
なんだ、次のようにすればよかったのだ。
template<class T> class X {
public:
T *foo() {
// 処理処理…
}
};
class A : public X <A> { };
class B : public X <B> { };
最後の行だけ見ると、何だか自己言及的で、どこかに矛盾がありそうだけど、 ちゃんとうまく動いている。 気持ち悪いけど、ちょっと気分がよい、そんな一日であった。
初出: 2001年6月13日
2001-11-28 追記: 『プログラミング言語 C++ 第3版』を読み返していたら、 「13.6 派生とテンプレート」というところに、 「基底クラスに派生クラス自身を渡すことに依存する面白いテクニックがある」 という記述とともに、本稿で述べたものと同じようなテクニックが紹介されておりました。 このあたりは最初に読んだときナナメ読みで飛ばしたからなあ。