しのはらのC++実験室トップページ
「移植性の高いプログラムの書き方」トップページ
#include <list>
class Fruit{
public:
virtual const char* color()const=0;
};
class Apple : public Fruit{
public:
const char* color()const{ return "red";}
};
class Banana : public Fruit{
public:
const char* color()const{ return "yellow";}
};
class Orange : public Fruit{
public:
const char* color()const{ return "orange";}
};
typedef std::list<Fruit*> Fruits;
このように基底クラスFruitを作るとうまく行きます。
しかし、AppleやBananaやOrangeの定義が予め与えられていて書き換えられない場合は、このような基底クラスを作れないので、困ってしまいます。
class Apple{
public:
const char* color()const{ return "red";}
};
class Banana{
public:
const char* color()const{ return "yellow";}
};
class Orange{
public:
const char* color()const{ return "orange";}
};
ここで目的としているのは、各クラスのオブジェクトを一つのlistに格納して、しかも「型」によって異なる結果を返す関数colorを正しく呼び出せるようにすることです。前述の通りこのような問題に対しては基底クラスと仮想関数によるオブジェクトの多態性を利用するのが普通ですが、AppleやBananaやOrangeには共通の基底クラスがありません。
#include <list>
// あとから付け足す基底クラス
class Fruit{
public:
virtual const char* color()const=0;
};
// AppleやBananaなどをFruitの派生クラスに見せかけるためのアダプタ
template <class T>
class FruitItem : public Fruit{
T x;
public:
FruitItem(){}
FruitItem(const T& y):x(y){}
const char* color()const{ return x.color(); }
};
typedef std::list<Fruit*> Fruits;
はい、これだけです。これによって次のようなことができます。
#include <iostream>
int main()
{
Fruits fruits;
FruitItem<Apple> apple;
FruitItem<Banana> banana;
FruitItem<Orange> orange;
fruits.push_back(&apple);
fruits.push_back(&banana);
fruits.push_back(&orange);
for(Fruits::iterator it = fruits.begin();
it != fruits.end(); ++it ){
std::cout << (*it)->color() << std::endl;
}
}
まったく別々に作られたはずのAppleやBananaとそのメンバ関数colorが、あたかも同じ基底クラスを持ち、その仮想関数を実装しているかのように振る舞います。これはテンプレート機能のなせる技です。テンプレートクラスFruitItemは、メンバ関数colorを持つような任意のクラスを引数にとって、Fruitクラスの派生クラスを作るものです。FruitItemクラスのメンバ関数colorはFruitクラスの仮想関数colorを実装していますが、その中身は実際のクラス(AppleやBanana)を呼び出すようになっています。
こうすることでFruitItem<Apple>はAppleのように振る舞い、しかもFruitの派生クラスです。同様にFruitItem<Banana>やFruitItem<Orange>もです。
こうして無関係だったクラス同志がめでたく統一されたわけです。