移植性の高いプログラムの書き方
「基底クラスをあとから作る方法」編

しのはらのC++実験室トップページ
「移植性の高いプログラムの書き方」トップページ

異なる型をまとめて扱いたい

 C言語にはたくさんの型があります。構造体やクラスを作ればどんどん増えていきます。時には一つのプログラムで、異なる型のオブジェクトをまとめて管理したいこともあります。例えば、AppleクラスとBananaクラスとOrangeクラスがあって、それらのオブジェクトをまとめてlistに格納したいとします。普通は次のようなプログラムを書きます。
#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の定義が予め与えられていて書き換えられない場合は、このような基底クラスを作れないので、困ってしまいます。

基底クラスをあとから作る

 まず3つのクラスが次のように与えられているとします。
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>もです。 こうして無関係だったクラス同志がめでたく統一されたわけです。

応用しよう

 と、ここまではちょっとした技巧の紹介でした。「移植性」とどんな関係があるかって?
 プログラムの移植性を高めるというのは、「完璧な移植性」を目指すものではなくて、できる限りあとで問題が起きにくいようにしておくという意味が強いと思います。移植という作業にはどうしても(なぜか)障害が起きるので、予め予想される範囲の障害には対処して置くわけです。しかし、あまり言われることが少ないのですが、問題が起きたときにうまく対処するための技術も「移植」のための重要なポイントだと思うのです。
 例えば、ある環境ではGUI部品(画面上のボタンやスクロールバー)を表すすべてのクラスが一つの基底クラスから派生しているとしても、別の環境ではそうではないかも知れません。でも「基底クラスのようなもの」があると便利ですから、上のテクニックを活用することができます。
 実は移植に限らす、例えばライブラリの仕様変更によっても同じような問題は起きます。他人の作ったライブラリの仕様変更によって、今まであった共通の基底クラスが利用できなくなることもないとは言えません。